oai 0.0.1 → 1.3.0
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.
- checksums.yaml +7 -0
- data/README.md +128 -0
- data/Rakefile +64 -0
- data/bin/oai +66 -0
- data/examples/models/file_model.rb +63 -0
- data/examples/providers/dublin_core.rb +474 -0
- data/lib/oai/{get_record.rb → client/get_record.rb} +5 -1
- data/lib/oai/client/header.rb +18 -0
- data/lib/oai/{identify.rb → client/identify.rb} +2 -0
- data/lib/oai/{list_identifiers.rb → client/list_identifiers.rb} +1 -0
- data/lib/oai/{list_records.rb → client/list_records.rb} +6 -5
- data/lib/oai/{list_sets.rb → client/list_sets.rb} +7 -6
- data/lib/oai/client/record.rb +27 -0
- data/lib/oai/client/response.rb +53 -0
- data/lib/oai/client/resumable.rb +42 -0
- data/lib/oai/client.rb +313 -108
- data/lib/oai/constants.rb +34 -0
- data/lib/oai/exception.rb +80 -1
- data/lib/oai/harvester/config.rb +41 -0
- data/lib/oai/harvester/harvest.rb +162 -0
- data/lib/oai/harvester/logging.rb +68 -0
- data/lib/oai/harvester/mailer.rb +17 -0
- data/lib/oai/harvester/shell.rb +337 -0
- data/lib/oai/harvester.rb +43 -0
- data/lib/oai/provider/metadata_format/oai_dc.rb +28 -0
- data/lib/oai/provider/metadata_format.rb +119 -0
- data/lib/oai/provider/model/activerecord_caching_wrapper.rb +129 -0
- data/lib/oai/provider/model/activerecord_wrapper.rb +217 -0
- data/lib/oai/provider/model.rb +80 -0
- data/lib/oai/provider/partial_result.rb +18 -0
- data/lib/oai/provider/response/error.rb +16 -0
- data/lib/oai/provider/response/get_record.rb +27 -0
- data/lib/oai/provider/response/identify.rb +37 -0
- data/lib/oai/provider/response/list_identifiers.rb +30 -0
- data/lib/oai/provider/response/list_metadata_formats.rb +39 -0
- data/lib/oai/provider/response/list_records.rb +47 -0
- data/lib/oai/provider/response/list_sets.rb +23 -0
- data/lib/oai/provider/response/record_response.rb +103 -0
- data/lib/oai/provider/response.rb +133 -0
- data/lib/oai/provider/resumption_token.rb +155 -0
- data/lib/oai/provider.rb +487 -0
- data/lib/oai/set.rb +14 -5
- data/lib/oai/xpath.rb +59 -5
- data/lib/oai.rb +7 -13
- data/lib/test.rb +25 -0
- data/test/activerecord_provider/config/connection.rb +30 -0
- data/test/activerecord_provider/database/0001_oaipmh_tables.rb +70 -0
- data/test/activerecord_provider/fixtures/dc.yml +1501 -0
- data/test/activerecord_provider/helpers/providers.rb +68 -0
- data/test/activerecord_provider/helpers/set_provider.rb +24 -0
- data/test/activerecord_provider/helpers/transactional_test_case.rb +35 -0
- data/test/activerecord_provider/models/dc_field.rb +15 -0
- data/test/activerecord_provider/models/dc_lang.rb +3 -0
- data/test/activerecord_provider/models/dc_set.rb +7 -0
- data/test/activerecord_provider/models/exclusive_set_dc_field.rb +17 -0
- data/test/activerecord_provider/models/oai_token.rb +3 -0
- data/test/activerecord_provider/tc_activerecord_wrapper.rb +63 -0
- data/test/activerecord_provider/tc_ar_provider.rb +199 -0
- data/test/activerecord_provider/tc_ar_sets_provider.rb +159 -0
- data/test/activerecord_provider/tc_caching_paging_provider.rb +50 -0
- data/test/activerecord_provider/tc_simple_paging_provider.rb +71 -0
- data/test/activerecord_provider/test_helper_ar_provider.rb +7 -0
- data/test/client/helpers/provider.rb +65 -0
- data/test/client/helpers/test_wrapper.rb +6 -0
- data/test/client/tc_exception.rb +36 -0
- data/test/client/tc_get_record.rb +39 -0
- data/test/client/tc_http_client.rb +129 -0
- data/test/client/tc_identify.rb +13 -0
- data/test/client/tc_libxml.rb +61 -0
- data/test/{tc_list_identifiers.rb → client/tc_list_identifiers.rb} +28 -9
- data/test/{tc_list_metadata_formats.rb → client/tc_list_metadata_formats.rb} +4 -1
- data/test/client/tc_list_records.rb +26 -0
- data/test/client/tc_list_sets.rb +34 -0
- data/test/client/tc_low_resolution_dates.rb +14 -0
- data/test/client/tc_utf8_escaping.rb +19 -0
- data/test/client/tc_xpath.rb +26 -0
- data/test/client/test_helper_client.rb +5 -0
- data/test/harvester/tc_harvest.rb +42 -0
- data/test/harvester/test_helper_harvester.rb +6 -0
- data/test/provider/models.rb +246 -0
- data/test/provider/tc_exceptions.rb +81 -0
- data/test/provider/tc_functional_tokens.rb +53 -0
- data/test/provider/tc_instance_provider.rb +41 -0
- data/test/provider/tc_provider.rb +136 -0
- data/test/provider/tc_resumption_tokens.rb +52 -0
- data/test/provider/tc_simple_provider.rb +138 -0
- data/test/provider/test_helper_provider.rb +62 -0
- data/test/test.xml +22 -0
- metadata +238 -56
- data/lib/oai/header.rb +0 -12
- data/lib/oai/record.rb +0 -11
- data/lib/oai/response.rb +0 -20
- data/test/tc_get_record.rb +0 -27
- data/test/tc_identify.rb +0 -8
- data/test/tc_list_records.rb +0 -9
- data/test/tc_list_sets.rb +0 -17
- data/test.rb +0 -12
- /data/lib/oai/{list_metadata_formats.rb → client/list_metadata_formats.rb} +0 -0
- /data/lib/oai/{metadata_format.rb → client/metadata_format.rb} +0 -0
data/lib/oai/provider.rb
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
require 'singleton'
|
|
3
|
+
require 'builder'
|
|
4
|
+
|
|
5
|
+
if not defined?(OAI::Const::VERBS)
|
|
6
|
+
require 'oai/exception'
|
|
7
|
+
require 'oai/constants'
|
|
8
|
+
require 'oai/xpath'
|
|
9
|
+
require 'oai/set'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
%w{ response metadata_format resumption_token model partial_result
|
|
13
|
+
response/record_response response/identify response/get_record
|
|
14
|
+
response/list_identifiers response/list_records
|
|
15
|
+
response/list_metadata_formats response/list_sets response/error
|
|
16
|
+
}.each { |lib| require File.dirname(__FILE__) + "/provider/#{lib}" }
|
|
17
|
+
|
|
18
|
+
if defined?(ActiveRecord)
|
|
19
|
+
require File.dirname(__FILE__) + "/provider/model/activerecord_wrapper"
|
|
20
|
+
require File.dirname(__FILE__) + "/provider/model/activerecord_caching_wrapper"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# # OAI::Provider
|
|
24
|
+
#
|
|
25
|
+
# Open Archives Initiative - Protocol for Metadata Harvesting see
|
|
26
|
+
# <http://www.openarchives.org/>
|
|
27
|
+
#
|
|
28
|
+
# ## Features
|
|
29
|
+
# * Easily setup a simple repository
|
|
30
|
+
# * Simple integration with ActiveRecord
|
|
31
|
+
# * Dublin Core metadata format included
|
|
32
|
+
# * Easily add addition metadata formats
|
|
33
|
+
# * Adaptable to any data source
|
|
34
|
+
# * Simple resumption token support
|
|
35
|
+
#
|
|
36
|
+
# ## Usage
|
|
37
|
+
#
|
|
38
|
+
# To create a functional provider either subclass {OAI::Provider::Base},
|
|
39
|
+
# or reconfigure the defaults.
|
|
40
|
+
#
|
|
41
|
+
# ### Sub classing a provider
|
|
42
|
+
#
|
|
43
|
+
# ```ruby
|
|
44
|
+
# class MyProvider < Oai::Provider
|
|
45
|
+
# repository_name 'My little OAI provider'
|
|
46
|
+
# repository_url 'http://localhost/provider'
|
|
47
|
+
# record_prefix 'oai:localhost'
|
|
48
|
+
# admin_email 'root@localhost' # String or Array
|
|
49
|
+
# source_model MyModel.new # Subclass of OAI::Provider::Model
|
|
50
|
+
# end
|
|
51
|
+
# ```
|
|
52
|
+
#
|
|
53
|
+
# ### Configuring the default provider
|
|
54
|
+
#
|
|
55
|
+
# ```ruby
|
|
56
|
+
# class Oai::Provider::Base
|
|
57
|
+
# repository_name 'My little OAI Provider'
|
|
58
|
+
# repository_url 'http://localhost/provider'
|
|
59
|
+
# record_prefix 'oai:localhost'
|
|
60
|
+
# admin_email 'root@localhost'
|
|
61
|
+
# # record_prefix will be automatically prepended to sample_id, so in this
|
|
62
|
+
# # case it becomes: oai:localhost:13900
|
|
63
|
+
# sample_id '13900'
|
|
64
|
+
# source_model MyModel.new
|
|
65
|
+
# end
|
|
66
|
+
# ```
|
|
67
|
+
#
|
|
68
|
+
# The provider does allow a URL to be passed in at request processing time
|
|
69
|
+
# in case the repository URL cannot be determined ahead of time.
|
|
70
|
+
#
|
|
71
|
+
# ## Integrating with frameworks
|
|
72
|
+
#
|
|
73
|
+
# ### Camping
|
|
74
|
+
#
|
|
75
|
+
# In the Models module of your camping application post model definition:
|
|
76
|
+
#
|
|
77
|
+
# ```ruby
|
|
78
|
+
# class CampingProvider < OAI::Provider::Base
|
|
79
|
+
# repository_name 'Camping Test OAI Repository'
|
|
80
|
+
# source_model ActiveRecordWrapper.new(YOUR_ACTIVE_RECORD_MODEL)
|
|
81
|
+
# end
|
|
82
|
+
# ```
|
|
83
|
+
#
|
|
84
|
+
# In the Controllers module:
|
|
85
|
+
#
|
|
86
|
+
# ```ruby
|
|
87
|
+
# class Oai
|
|
88
|
+
# def get
|
|
89
|
+
# @headers['Content-Type'] = 'text/xml'
|
|
90
|
+
# provider = Models::CampingProvider.new
|
|
91
|
+
# provider.process_request(@input.merge(:url => "http:"+URL(Oai).to_s))
|
|
92
|
+
# end
|
|
93
|
+
# end
|
|
94
|
+
# ```
|
|
95
|
+
#
|
|
96
|
+
# The provider will be available at "/oai"
|
|
97
|
+
#
|
|
98
|
+
# ### Rails
|
|
99
|
+
#
|
|
100
|
+
# At the bottom of environment.rb create a OAI Provider:
|
|
101
|
+
#
|
|
102
|
+
# ```ruby
|
|
103
|
+
# # forgive the standard blog example.
|
|
104
|
+
#
|
|
105
|
+
# require 'oai'
|
|
106
|
+
# class BlogProvider < OAI::Provider::Base
|
|
107
|
+
# repository_name 'My little OAI Provider'
|
|
108
|
+
# repository_url 'http://localhost:3000/provider'
|
|
109
|
+
# record_prefix 'oai:blog'
|
|
110
|
+
# admin_email 'root@localhost'
|
|
111
|
+
# source_model OAI::Provider::ActiveRecordWrapper.new(Post)
|
|
112
|
+
# sample_id '13900' # record prefix used, so becomes oai:blog:13900
|
|
113
|
+
# end
|
|
114
|
+
# ```
|
|
115
|
+
#
|
|
116
|
+
# Create a custom controller:
|
|
117
|
+
#
|
|
118
|
+
# ```ruby
|
|
119
|
+
# class OaiController < ApplicationController
|
|
120
|
+
# def index
|
|
121
|
+
# provider = BlogProvider.new
|
|
122
|
+
# response = provider.process_request(oai_params.to_h)
|
|
123
|
+
# render :body => response, :content_type => 'text/xml'
|
|
124
|
+
# end
|
|
125
|
+
#
|
|
126
|
+
# private
|
|
127
|
+
#
|
|
128
|
+
# def oai_params
|
|
129
|
+
# params.permit(:verb, :identifier, :metadataPrefix, :set, :from, :until, :resumptionToken)
|
|
130
|
+
# end
|
|
131
|
+
# end
|
|
132
|
+
# ```
|
|
133
|
+
#
|
|
134
|
+
# And route to it in your `config/routes.rb` file:
|
|
135
|
+
#
|
|
136
|
+
# ```ruby
|
|
137
|
+
# match 'oai', to: "oai#index", via: [:get, :post]
|
|
138
|
+
# ```
|
|
139
|
+
#
|
|
140
|
+
# Special thanks to Jose Hales-Garcia for this solution.
|
|
141
|
+
#
|
|
142
|
+
# ### Leverage the Provider instance
|
|
143
|
+
#
|
|
144
|
+
# The traditional implementation of the OAI::Provider would pass the OAI::Provider
|
|
145
|
+
# class to the different resposnes. This made it hard to inject context into a
|
|
146
|
+
# common provider. Consider that we might have different request headers that
|
|
147
|
+
# change the scope of the OAI::Provider queries.
|
|
148
|
+
#
|
|
149
|
+
# ```ruby
|
|
150
|
+
# class InstanceProvider
|
|
151
|
+
# def initialize(options = {})
|
|
152
|
+
# super({ :provider_context => :instance_based })
|
|
153
|
+
# @controller = options.fetch(:controller)
|
|
154
|
+
# end
|
|
155
|
+
# attr_reader :controller
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
# class OaiController < ApplicationController
|
|
159
|
+
# def index
|
|
160
|
+
# provider = InstanceProvider.new({ :controller => self })
|
|
161
|
+
# request_body = provider.process_request(oai_params.to_h)
|
|
162
|
+
# render :body => request_body, :content_type => 'text/xml'
|
|
163
|
+
# end
|
|
164
|
+
# ```
|
|
165
|
+
#
|
|
166
|
+
# In the above example, the underlying response object will now receive an
|
|
167
|
+
# instance of the InstanceProvider. Without the `super({ :provider_context => :instance_based })`
|
|
168
|
+
# the response objects would have received the class InstanceProvider as the
|
|
169
|
+
# given provider.
|
|
170
|
+
#
|
|
171
|
+
# ## Supporting custom metadata formats
|
|
172
|
+
#
|
|
173
|
+
# See {OAI::MetadataFormat} for details.
|
|
174
|
+
#
|
|
175
|
+
# ## ActiveRecord Integration
|
|
176
|
+
#
|
|
177
|
+
# ActiveRecord integration is provided by the `ActiveRecordWrapper` class.
|
|
178
|
+
# It takes one required paramater, the class name of the AR class to wrap,
|
|
179
|
+
# and optional hash of options.
|
|
180
|
+
#
|
|
181
|
+
# As of `oai` gem 1.0.0, Rails 5.2.x and Rails 6.0.x are supported.
|
|
182
|
+
# Please check the .travis.yml file at root of repo to see what versions of ruby/rails
|
|
183
|
+
# are being tested, in case this is out of date.
|
|
184
|
+
#
|
|
185
|
+
# Valid options include:
|
|
186
|
+
#
|
|
187
|
+
# * `timestamp_field` - Specifies the model field/method to use as the update
|
|
188
|
+
# filter. Defaults to `updated_at`.
|
|
189
|
+
# * `identifier_field` -- specifies the model field/method to use to get value to use
|
|
190
|
+
# as oai identifier (method return value should not include prefix)
|
|
191
|
+
# * `limit` - Maximum number of records to return in each page/set.
|
|
192
|
+
# Defaults to 100, set to `nil` for all records in one page. Otherwise
|
|
193
|
+
# the wrapper will paginate the result via resumption tokens.
|
|
194
|
+
# _Caution: specifying too large a limit will adversely affect performance._
|
|
195
|
+
#
|
|
196
|
+
# Mapping from a ActiveRecord object to a specific metadata format follows
|
|
197
|
+
# this set of rules:
|
|
198
|
+
#
|
|
199
|
+
# 1. Does `Model#to_{metadata_prefix}` exist? If so just return the result.
|
|
200
|
+
# 2. Does the model provide a map via `Model.map_{metadata_prefix}`? If so
|
|
201
|
+
# use the map to generate the xml document.
|
|
202
|
+
# 3. Loop thru the fields of the metadata format and check to see if the
|
|
203
|
+
# model responds to either the plural, or singular of the field.
|
|
204
|
+
#
|
|
205
|
+
# For maximum control of the xml metadata generated, it's usually best to
|
|
206
|
+
# provide a `to_{metadata_prefix}` in the model. If using Builder be sure
|
|
207
|
+
# not to include any `instruct!` in the xml object.
|
|
208
|
+
#
|
|
209
|
+
# ### Explicit creation example
|
|
210
|
+
#
|
|
211
|
+
# ```ruby
|
|
212
|
+
# class Post < ActiveRecord::Base
|
|
213
|
+
# def to_oai_dc
|
|
214
|
+
# xml = Builder::XmlMarkup.new
|
|
215
|
+
# xml.tag!("oai_dc:dc",
|
|
216
|
+
# 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
|
|
217
|
+
# 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
|
|
218
|
+
# 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
|
219
|
+
# 'xsi:schemaLocation' =>
|
|
220
|
+
# %{http://www.openarchives.org/OAI/2.0/oai_dc/
|
|
221
|
+
# http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
|
|
222
|
+
# xml.tag!('oai_dc:title', title)
|
|
223
|
+
# xml.tag!('oai_dc:description', text)
|
|
224
|
+
# xml.tag!('oai_dc:creator', user)
|
|
225
|
+
# tags.each do |tag|
|
|
226
|
+
# xml.tag!('oai_dc:subject', tag)
|
|
227
|
+
# end
|
|
228
|
+
# end
|
|
229
|
+
# xml.target!
|
|
230
|
+
# end
|
|
231
|
+
# end
|
|
232
|
+
# ```
|
|
233
|
+
#
|
|
234
|
+
# ### Mapping Example
|
|
235
|
+
#
|
|
236
|
+
# ```ruby
|
|
237
|
+
# # Extremely contrived mapping
|
|
238
|
+
# class Post < ActiveRecord::Base
|
|
239
|
+
# def self.map_oai_dc
|
|
240
|
+
# {:subject => :tags,
|
|
241
|
+
# :description => :text,
|
|
242
|
+
# :creator => :user,
|
|
243
|
+
# :contibutor => :comments}
|
|
244
|
+
# end
|
|
245
|
+
# end
|
|
246
|
+
# ```
|
|
247
|
+
# ### Scopes for restrictions or eager-loading
|
|
248
|
+
#
|
|
249
|
+
# Instead of passing in a Model class to OAI::Provider::ActiveRecordWrapper, you can actually
|
|
250
|
+
# pass in any scope (or ActiveRecord::Relation). This means you can use it for restrictions:
|
|
251
|
+
#
|
|
252
|
+
# OAI::Provider::ActiveRecordWrapper.new(Post.where(published: true))
|
|
253
|
+
#
|
|
254
|
+
# Or eager-loading an association you will need to create serialization, to avoid n+1 query
|
|
255
|
+
# performance problems:
|
|
256
|
+
#
|
|
257
|
+
# OAI::Provider::ActiveRecordWrapper.new(Post.includes(:categories))
|
|
258
|
+
#
|
|
259
|
+
# Or both of those in combination, or anything else that returns an ActiveRecord::Relation,
|
|
260
|
+
# including using custom scopes, etc.
|
|
261
|
+
#
|
|
262
|
+
# ### Sets?
|
|
263
|
+
#
|
|
264
|
+
# There is some code written to support oai-pmh "sets" in the ActiveRecord::Wrapper, but
|
|
265
|
+
# it's somewhat inflexible, and not well-documented, and as I write this I don't understand
|
|
266
|
+
# it enough to say more. See https://github.com/code4lib/ruby-oai/issues/67
|
|
267
|
+
#
|
|
268
|
+
module OAI::Provider
|
|
269
|
+
class Base
|
|
270
|
+
include OAI::Provider
|
|
271
|
+
|
|
272
|
+
class << self
|
|
273
|
+
attr_reader :formats
|
|
274
|
+
attr_accessor :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
|
|
275
|
+
|
|
276
|
+
def register_format(format)
|
|
277
|
+
@formats ||= {}
|
|
278
|
+
@formats[format.prefix] = format
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def format_supported?(prefix)
|
|
282
|
+
@formats.keys.include?(prefix)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def format(prefix)
|
|
286
|
+
if @formats[prefix].nil?
|
|
287
|
+
raise OAI::FormatException.new
|
|
288
|
+
else
|
|
289
|
+
@formats[prefix]
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
protected
|
|
294
|
+
|
|
295
|
+
def inherited(klass)
|
|
296
|
+
self.instance_variables.each do |iv|
|
|
297
|
+
klass.instance_variable_set(iv, self.instance_variable_get(iv))
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
alias_method :repository_name, :name=
|
|
302
|
+
alias_method :repository_url, :url=
|
|
303
|
+
alias_method :record_prefix, :prefix=
|
|
304
|
+
alias_method :admin_email, :email=
|
|
305
|
+
alias_method :deletion_support, :delete_support=
|
|
306
|
+
alias_method :update_granularity, :granularity=
|
|
307
|
+
alias_method :source_model, :model=
|
|
308
|
+
alias_method :sample_id, :identifier=
|
|
309
|
+
alias_method :extra_description, :description=
|
|
310
|
+
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Default configuration of a repository
|
|
314
|
+
Base.repository_name 'Open Archives Initiative Data Provider'
|
|
315
|
+
Base.repository_url 'unknown'
|
|
316
|
+
Base.record_prefix 'oai:localhost'
|
|
317
|
+
Base.admin_email 'nobody@localhost'
|
|
318
|
+
Base.deletion_support OAI::Const::Delete::TRANSIENT
|
|
319
|
+
Base.update_granularity OAI::Const::Granularity::HIGH
|
|
320
|
+
Base.sample_id '13900'
|
|
321
|
+
|
|
322
|
+
Base.register_format(OAI::Provider::Metadata::DublinCore.instance)
|
|
323
|
+
|
|
324
|
+
PROVIDER_CONTEXTS = {
|
|
325
|
+
:class_based => :class_based,
|
|
326
|
+
:instance_based => :instance_based
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
def initialize(options = {})
|
|
330
|
+
provider_context = options.fetch(:provider_context) { :class_based }
|
|
331
|
+
@provider_context = PROVIDER_CONTEXTS.fetch(provider_context)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# @note These are the accessor methods on the class. If you need to overwrite
|
|
335
|
+
# them on the instance level you can do that. However, an instance of this
|
|
336
|
+
# class won't be used unless you initialize with:
|
|
337
|
+
# { :provider_context => :instance_based }
|
|
338
|
+
attr_writer :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
|
|
339
|
+
|
|
340
|
+
# The traditional interaction of a Provider has been to:
|
|
341
|
+
#
|
|
342
|
+
# 1) Assign attributes to the Provider class
|
|
343
|
+
# 2) Instantiate the Provider class
|
|
344
|
+
# 3) Call response instance methods for theProvider which pass
|
|
345
|
+
# the Provider class and not the instance.
|
|
346
|
+
#
|
|
347
|
+
# The above behavior continues unless you initialize the Provider with
|
|
348
|
+
# { :provider_context => :instance_based }. If you do that, then the
|
|
349
|
+
# Provider behavior will be:
|
|
350
|
+
#
|
|
351
|
+
# 1) Assign attributes to Provider class
|
|
352
|
+
# 2) Instantiate the Provider class
|
|
353
|
+
# 3) Call response instance methods for theProvider which pass an
|
|
354
|
+
# instance of the Provider to those response objects.
|
|
355
|
+
# a) The instance will mirror all of the assigned Provider class
|
|
356
|
+
# attributes, but allows for overriding and extending on a
|
|
357
|
+
# case by case basis.
|
|
358
|
+
# (Dear reader, please note the second behavior is something most
|
|
359
|
+
# of us would've assumed to be the case, but for historic now lost
|
|
360
|
+
# reasons is not the case.)
|
|
361
|
+
def provider_context
|
|
362
|
+
if @provider_context == :instance_based
|
|
363
|
+
self
|
|
364
|
+
else
|
|
365
|
+
self.class
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def format_supported?(*args)
|
|
370
|
+
self.class.format_supported?(*args)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def format(*args)
|
|
374
|
+
self.class.format(*args)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def formats
|
|
378
|
+
self.class.formats
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def name
|
|
382
|
+
@name || self.class.name
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def url
|
|
386
|
+
@url || self.class.url
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def prefix
|
|
390
|
+
@prefix || self.class.prefix
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def email
|
|
394
|
+
@email || self.class.email
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def delete_support
|
|
398
|
+
@delete_support || self.class.delete_support
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def granularity
|
|
402
|
+
@granularity || self.class.granularity
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def model
|
|
406
|
+
@model || self.class.model
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def identifier
|
|
410
|
+
@identifier || self.class.identifier
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def description
|
|
414
|
+
@description || self.class.description
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Equivalent to '&verb=Identify', returns information about the repository
|
|
418
|
+
def identify(options = {})
|
|
419
|
+
Response::Identify.new(provider_context, options).to_xml
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Equivalent to '&verb=ListSets', returns a list of sets that are supported
|
|
423
|
+
# by the repository or an error if sets are not supported.
|
|
424
|
+
def list_sets(options = {})
|
|
425
|
+
Response::ListSets.new(provider_context, options).to_xml
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Equivalent to '&verb=ListMetadataFormats', returns a list of metadata formats
|
|
429
|
+
# supported by the repository.
|
|
430
|
+
def list_metadata_formats(options = {})
|
|
431
|
+
Response::ListMetadataFormats.new(provider_context, options).to_xml
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Equivalent to '&verb=ListIdentifiers', returns a list of record headers that
|
|
435
|
+
# meet the supplied criteria.
|
|
436
|
+
def list_identifiers(options = {})
|
|
437
|
+
Response::ListIdentifiers.new(provider_context, options).to_xml
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Equivalent to '&verb=ListRecords', returns a list of records that meet the
|
|
441
|
+
# supplied criteria.
|
|
442
|
+
def list_records(options = {})
|
|
443
|
+
Response::ListRecords.new(provider_context, options).to_xml
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Equivalent to '&verb=GetRecord', returns a record matching the required
|
|
447
|
+
# :identifier option
|
|
448
|
+
def get_record(options = {})
|
|
449
|
+
Response::GetRecord.new(provider_context, options).to_xml
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# xml_response = process_verb('ListRecords', :from => 'October 1, 2005',
|
|
453
|
+
# :until => 'November 1, 2005')
|
|
454
|
+
#
|
|
455
|
+
# If you are implementing a web interface using process_request is the
|
|
456
|
+
# preferred way.
|
|
457
|
+
def process_request(params = {})
|
|
458
|
+
begin
|
|
459
|
+
|
|
460
|
+
# Allow the request to pass in a url
|
|
461
|
+
provider_context.url = params['url'] ? params.delete('url') : self.url
|
|
462
|
+
|
|
463
|
+
verb = params.delete('verb') || params.delete(:verb)
|
|
464
|
+
|
|
465
|
+
unless verb and OAI::Const::VERBS.keys.include?(verb)
|
|
466
|
+
raise OAI::VerbException.new
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
send(methodize(verb), params)
|
|
470
|
+
|
|
471
|
+
rescue => err
|
|
472
|
+
if err.respond_to?(:code)
|
|
473
|
+
Response::Error.new(self.class, err).to_xml
|
|
474
|
+
else
|
|
475
|
+
raise err
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Convert valid OAI-PMH verbs into ruby method calls
|
|
481
|
+
def methodize(verb)
|
|
482
|
+
verb.gsub(/[A-Z]/) {|m| "_#{m.downcase}"}.sub(/^\_/,'')
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
end
|
data/lib/oai/set.rb
CHANGED
|
@@ -7,12 +7,21 @@ module OAI
|
|
|
7
7
|
include OAI::XPath
|
|
8
8
|
attr_accessor :name, :spec, :description
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@name =
|
|
12
|
-
@spec =
|
|
13
|
-
@description =
|
|
10
|
+
def initialize(values = {})
|
|
11
|
+
@name = values.delete(:name)
|
|
12
|
+
@spec = values.delete(:spec)
|
|
13
|
+
@description = values.delete(:description)
|
|
14
|
+
raise ArgumentException, "Invalid options" unless values.empty?
|
|
14
15
|
end
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
def self.parse(element)
|
|
18
|
+
set = self.new
|
|
19
|
+
set.name = set.xpath(element, './/setName')
|
|
20
|
+
set.spec = set.xpath(element, './/setSpec')
|
|
21
|
+
set.description = set.xpath_first(element, './/setDescription')
|
|
22
|
+
set
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
def to_s
|
|
17
26
|
"#{@name} [#{@spec}]"
|
|
18
27
|
end
|
data/lib/oai/xpath.rb
CHANGED
|
@@ -1,21 +1,75 @@
|
|
|
1
|
-
require 'rexml/xpath'
|
|
2
|
-
|
|
3
1
|
module OAI
|
|
4
2
|
module XPath
|
|
3
|
+
|
|
4
|
+
# get all matching nodes
|
|
5
5
|
def xpath_all(doc, path)
|
|
6
|
-
|
|
6
|
+
case parser_type(doc)
|
|
7
|
+
when 'libxml'
|
|
8
|
+
return doc.find(path).to_a if doc.find(path)
|
|
9
|
+
when 'rexml'
|
|
10
|
+
return REXML::XPath.match(doc, path)
|
|
11
|
+
end
|
|
12
|
+
return []
|
|
7
13
|
end
|
|
8
14
|
|
|
15
|
+
# get first matching node
|
|
9
16
|
def xpath_first(doc, path)
|
|
10
17
|
elements = xpath_all(doc, path)
|
|
11
18
|
return elements[0] if elements != nil
|
|
12
19
|
return nil
|
|
13
20
|
end
|
|
14
21
|
|
|
22
|
+
# get text for first matching node
|
|
15
23
|
def xpath(doc, path)
|
|
16
|
-
|
|
17
|
-
return
|
|
24
|
+
el = xpath_first(doc, path)
|
|
25
|
+
return unless el
|
|
26
|
+
case parser_type(doc)
|
|
27
|
+
when 'libxml'
|
|
28
|
+
return el.content
|
|
29
|
+
when 'rexml'
|
|
30
|
+
return el.text
|
|
31
|
+
end
|
|
18
32
|
return nil
|
|
19
33
|
end
|
|
34
|
+
|
|
35
|
+
# figure out an attribute
|
|
36
|
+
def get_attribute(node, attr_name)
|
|
37
|
+
case node.class.to_s
|
|
38
|
+
when 'REXML::Element'
|
|
39
|
+
return node.attribute(attr_name)
|
|
40
|
+
when 'LibXML::XML::Node'
|
|
41
|
+
#There has been a method shift between 0.5 and 0.7
|
|
42
|
+
if defined?(node.property) == nil
|
|
43
|
+
return node.attributes[attr_name]
|
|
44
|
+
else
|
|
45
|
+
#node.property is being deprecated. We'll eventually remove
|
|
46
|
+
#this trap
|
|
47
|
+
begin
|
|
48
|
+
return node[attr_name]
|
|
49
|
+
rescue
|
|
50
|
+
return node.property(attr_name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
return nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# figure out what sort of object we should do xpath on
|
|
60
|
+
def parser_type(x)
|
|
61
|
+
case x.class.to_s
|
|
62
|
+
when 'LibXML::XML::Document'
|
|
63
|
+
return 'libxml'
|
|
64
|
+
when 'LibXML::XML::Node'
|
|
65
|
+
return 'libxml'
|
|
66
|
+
when 'LibXML::XML::Node::Set'
|
|
67
|
+
return 'libxml'
|
|
68
|
+
when 'REXML::Element'
|
|
69
|
+
return 'rexml'
|
|
70
|
+
when 'REXML::Document'
|
|
71
|
+
return 'rexml'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
20
74
|
end
|
|
21
75
|
end
|
data/lib/oai.rb
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
|
|
4
|
-
require
|
|
5
|
-
|
|
6
|
-
require 'oai/set'
|
|
7
|
-
require 'oai/metadata_format'
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'date'
|
|
3
|
+
|
|
4
|
+
# Sub projects (client, provider) require their own libraries so the user
|
|
5
|
+
# can selectively load them.
|
|
8
6
|
require 'oai/client'
|
|
9
|
-
require 'oai/
|
|
10
|
-
|
|
11
|
-
require 'oai/list_metadata_formats'
|
|
12
|
-
require 'oai/get_record'
|
|
13
|
-
require 'oai/list_records'
|
|
14
|
-
require 'oai/list_sets'
|
|
7
|
+
require 'oai/provider'
|
|
8
|
+
|
data/lib/test.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'oai'
|
|
2
|
+
|
|
3
|
+
buffer = ""
|
|
4
|
+
start_time = Time.now()
|
|
5
|
+
|
|
6
|
+
client = OAI::Client.new 'http://digitalcollections.library.oregonstate.edu/cgi-bin/oai.exe', :parser =>'libxml'
|
|
7
|
+
|
|
8
|
+
last_check = Date.new(2006,9,5)
|
|
9
|
+
records = client.list_records
|
|
10
|
+
# :set => 'archives', :metadata_prefix => 'oai_dc', :from => last_check
|
|
11
|
+
|
|
12
|
+
x = 0
|
|
13
|
+
records.each do |record|
|
|
14
|
+
#fields = record.serialize_metadata(record.metadata, "oai_dc", "Oai_Dc")
|
|
15
|
+
#puts "Primary Title: " + fields.title[0] + "\n"
|
|
16
|
+
puts "Identifier: " + record.header.identifier + "\n"
|
|
17
|
+
x += 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end_time = Time.now()
|
|
21
|
+
|
|
22
|
+
puts buffer
|
|
23
|
+
puts "Time to run: " + (end_time - start_time).to_s + "\n"
|
|
24
|
+
puts "Records returned: " + x.to_s
|
|
25
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'logger'
|
|
3
|
+
|
|
4
|
+
# Configure AR connection
|
|
5
|
+
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
6
|
+
|
|
7
|
+
if RUBY_PLATFORM == "java"
|
|
8
|
+
require 'jdbc/sqlite3'
|
|
9
|
+
Jdbc::SQLite3.load_driver
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
ActiveRecord::Migration.verbose = false
|
|
13
|
+
ActiveRecord::Base.establish_connection :adapter => "sqlite3",
|
|
14
|
+
:database => ":memory:"
|
|
15
|
+
|
|
16
|
+
if ActiveRecord.version < Gem::Version.new("6.0.0")
|
|
17
|
+
ActiveRecord::MigrationContext.new(
|
|
18
|
+
File.join(File.dirname(__FILE__), '..', 'database')
|
|
19
|
+
).migrate
|
|
20
|
+
elsif ActiveRecord.version < Gem::Version.new("7.2.0")
|
|
21
|
+
ActiveRecord::MigrationContext.new(
|
|
22
|
+
File.join(File.dirname(__FILE__), '..', 'database'),
|
|
23
|
+
ActiveRecord::Base.connection.schema_migration
|
|
24
|
+
).migrate
|
|
25
|
+
else
|
|
26
|
+
ActiveRecord::MigrationContext.new(
|
|
27
|
+
File.join(File.dirname(__FILE__), '..', 'database')
|
|
28
|
+
).migrate
|
|
29
|
+
end
|
|
30
|
+
|