oai_talia 0.0.13
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.
- data/README +81 -0
- data/Rakefile +127 -0
- data/bin/oai +68 -0
- data/examples/models/file_model.rb +63 -0
- data/examples/providers/dublin_core.rb +474 -0
- data/lib/oai/client/get_record.rb +15 -0
- data/lib/oai/client/header.rb +18 -0
- data/lib/oai/client/identify.rb +30 -0
- data/lib/oai/client/list_identifiers.rb +12 -0
- data/lib/oai/client/list_metadata_formats.rb +12 -0
- data/lib/oai/client/list_records.rb +21 -0
- data/lib/oai/client/list_sets.rb +19 -0
- data/lib/oai/client/metadata_format.rb +12 -0
- data/lib/oai/client/record.rb +26 -0
- data/lib/oai/client/response.rb +35 -0
- data/lib/oai/client.rb +301 -0
- data/lib/oai/constants.rb +34 -0
- data/lib/oai/exception.rb +75 -0
- data/lib/oai/harvester/config.rb +41 -0
- data/lib/oai/harvester/harvest.rb +150 -0
- data/lib/oai/harvester/logging.rb +70 -0
- data/lib/oai/harvester/mailer.rb +17 -0
- data/lib/oai/harvester/shell.rb +338 -0
- data/lib/oai/harvester.rb +39 -0
- data/lib/oai/provider/metadata_format/oai_dc.rb +29 -0
- data/lib/oai/provider/metadata_format/oai_europeana.rb +38 -0
- data/lib/oai/provider/metadata_format.rb +143 -0
- data/lib/oai/provider/model/activerecord_caching_wrapper.rb +134 -0
- data/lib/oai/provider/model/activerecord_wrapper.rb +139 -0
- data/lib/oai/provider/model.rb +74 -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 +26 -0
- data/lib/oai/provider/response/identify.rb +25 -0
- data/lib/oai/provider/response/list_identifiers.rb +35 -0
- data/lib/oai/provider/response/list_metadata_formats.rb +34 -0
- data/lib/oai/provider/response/list_records.rb +34 -0
- data/lib/oai/provider/response/list_sets.rb +23 -0
- data/lib/oai/provider/response/record_response.rb +70 -0
- data/lib/oai/provider/response.rb +161 -0
- data/lib/oai/provider/resumption_token.rb +106 -0
- data/lib/oai/provider.rb +304 -0
- data/lib/oai/set.rb +29 -0
- data/lib/oai/xpath.rb +75 -0
- data/lib/oai.rb +8 -0
- data/lib/test.rb +25 -0
- data/test/activerecord_provider/config/connection.rb +5 -0
- data/test/activerecord_provider/config/database.yml +6 -0
- data/test/activerecord_provider/database/ar_migration.rb +59 -0
- data/test/activerecord_provider/database/oaipmhtest +0 -0
- data/test/activerecord_provider/fixtures/dc.yml +1501 -0
- data/test/activerecord_provider/helpers/providers.rb +44 -0
- data/test/activerecord_provider/helpers/set_provider.rb +36 -0
- data/test/activerecord_provider/models/dc_field.rb +7 -0
- data/test/activerecord_provider/models/dc_set.rb +6 -0
- data/test/activerecord_provider/models/oai_token.rb +3 -0
- data/test/activerecord_provider/tc_ar_provider.rb +113 -0
- data/test/activerecord_provider/tc_ar_sets_provider.rb +72 -0
- data/test/activerecord_provider/tc_caching_paging_provider.rb +55 -0
- data/test/activerecord_provider/tc_simple_paging_provider.rb +57 -0
- data/test/activerecord_provider/test_helper.rb +4 -0
- data/test/client/helpers/provider.rb +68 -0
- data/test/client/helpers/test_wrapper.rb +11 -0
- data/test/client/tc_exception.rb +36 -0
- data/test/client/tc_get_record.rb +37 -0
- data/test/client/tc_identify.rb +13 -0
- data/test/client/tc_libxml.rb +61 -0
- data/test/client/tc_list_identifiers.rb +52 -0
- data/test/client/tc_list_metadata_formats.rb +18 -0
- data/test/client/tc_list_records.rb +13 -0
- data/test/client/tc_list_sets.rb +19 -0
- data/test/client/tc_low_resolution_dates.rb +14 -0
- data/test/client/tc_utf8_escaping.rb +11 -0
- data/test/client/tc_xpath.rb +26 -0
- data/test/client/test_helper.rb +5 -0
- data/test/provider/models.rb +234 -0
- data/test/provider/tc_exceptions.rb +96 -0
- data/test/provider/tc_functional_tokens.rb +43 -0
- data/test/provider/tc_provider.rb +71 -0
- data/test/provider/tc_resumption_tokens.rb +46 -0
- data/test/provider/tc_simple_provider.rb +92 -0
- data/test/provider/test_helper.rb +36 -0
- data/test/test.xml +22 -0
- metadata +181 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module OAI::Provider
|
4
|
+
|
5
|
+
# ActiveRecord model class in support of the caching wrapper.
|
6
|
+
class OaiToken < ActiveRecord::Base
|
7
|
+
has_many :entries, :class_name => 'OaiEntry',
|
8
|
+
:order => "record_id", :dependent => :destroy
|
9
|
+
|
10
|
+
validates_uniqueness_of :token
|
11
|
+
|
12
|
+
# Make sanitize_sql a public method so we can make use of it.
|
13
|
+
public
|
14
|
+
|
15
|
+
def self.sanitize_sql(*arg)
|
16
|
+
super(*arg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def new_record_before_save?
|
20
|
+
@new_record_before_save
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# ActiveRecord model class in support of the caching wrapper.
|
26
|
+
class OaiEntry < ActiveRecord::Base
|
27
|
+
belongs_to :oai_token
|
28
|
+
|
29
|
+
validates_uniqueness_of :record_id, :scope => :oai_token
|
30
|
+
end
|
31
|
+
|
32
|
+
# = OAI::Provider::ActiveRecordCachingWrapper
|
33
|
+
#
|
34
|
+
# This class wraps an ActiveRecord model and delegates all of the record
|
35
|
+
# selection/retrieval to the AR model. It accepts options for specifying
|
36
|
+
# the update timestamp field, a timeout, and a limit. The limit option
|
37
|
+
# is used for doing pagination with resumption tokens. The timeout is
|
38
|
+
# used to expire old tokens from the cache. Default timeout is 12 hours.
|
39
|
+
#
|
40
|
+
# The difference between ActiveRecordWrapper and this class is how the
|
41
|
+
# pagination is accomplished. ActiveRecordWrapper encodes all the
|
42
|
+
# information in the token. That approach should work 99% of the time.
|
43
|
+
# If you have an extremely active respository you may want to consider
|
44
|
+
# the caching wrapper. The caching wrapper takes the entire result set
|
45
|
+
# from a request and caches it in another database table, well tables
|
46
|
+
# actually. So the result returned to the client will always be
|
47
|
+
# internally consistent.
|
48
|
+
#
|
49
|
+
class ActiveRecordCachingWrapper < ActiveRecordWrapper
|
50
|
+
|
51
|
+
attr_reader :model, :timestamp_field, :expire
|
52
|
+
|
53
|
+
def initialize(model, options={})
|
54
|
+
@expire = options.delete(:timeout) || 12.hours
|
55
|
+
super(model, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find(selector, options={})
|
59
|
+
sweep_cache
|
60
|
+
return next_set(options[:resumption_token]) if options[:resumption_token]
|
61
|
+
|
62
|
+
conditions = sql_conditions(options)
|
63
|
+
|
64
|
+
if :all == selector
|
65
|
+
total = model.count(:id, :conditions => conditions)
|
66
|
+
if @limit && total > @limit
|
67
|
+
select_partial(
|
68
|
+
ResumptionToken.new(options.merge({:last => 0})))
|
69
|
+
else
|
70
|
+
model.find(:all, :conditions => conditions)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
model.find(selector, :conditions => conditions)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def next_set(token_string)
|
80
|
+
raise ResumptionTokenException.new unless @limit
|
81
|
+
|
82
|
+
token = ResumptionToken.parse(token_string)
|
83
|
+
total = model.count(:id, :conditions => token_conditions(token))
|
84
|
+
|
85
|
+
if token.last * @limit + @limit < total
|
86
|
+
select_partial(token)
|
87
|
+
else
|
88
|
+
select_partial(token).records
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# select a subset of the result set, and return it with a
|
93
|
+
# resumption token to get the next subset
|
94
|
+
def select_partial(token)
|
95
|
+
if 0 == token.last
|
96
|
+
oaitoken = OaiToken.find_or_create_by_token(token.to_s)
|
97
|
+
if oaitoken.new_record_before_save?
|
98
|
+
OaiToken.connection.execute("insert into " +
|
99
|
+
"#{OaiEntry.table_name} (oai_token_id, record_id) " +
|
100
|
+
"select #{oaitoken.id}, id from #{model.table_name} where " +
|
101
|
+
"#{OaiToken.sanitize_sql(token_conditions(token))}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
oaitoken = OaiToken.find_by_token(token.to_s)
|
106
|
+
raise ResumptionTokenException.new unless oaitoken
|
107
|
+
|
108
|
+
PartialResult.new(
|
109
|
+
hydrate_records(oaitoken.entries.find(:all, :limit => @limit,
|
110
|
+
:offset => token.last * @limit)), token.next(token.last + 1)
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
def sweep_cache
|
115
|
+
OaiToken.destroy_all(["created_at < ?", Time.now - expire])
|
116
|
+
end
|
117
|
+
|
118
|
+
def hydrate_records(records)
|
119
|
+
model.find(records.collect {|r| r.record_id })
|
120
|
+
end
|
121
|
+
|
122
|
+
def token_conditions(token)
|
123
|
+
sql_conditions token.to_conditions_hash
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def expires_at(creation)
|
129
|
+
created = Time.parse(creation.strftime("%Y-%m-%d %H:%M:%S"))
|
130
|
+
created.utc + expire
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
module OAI::Provider
|
3
|
+
# = OAI::Provider::ActiveRecordWrapper
|
4
|
+
#
|
5
|
+
# This class wraps an ActiveRecord model and delegates all of the record
|
6
|
+
# selection/retrieval to the AR model. It accepts options for specifying
|
7
|
+
# the update timestamp field, a timeout, and a limit. The limit option
|
8
|
+
# is used for doing pagination with resumption tokens. The
|
9
|
+
# expiration timeout is ignored, since all necessary information is
|
10
|
+
# encoded in the token.
|
11
|
+
#
|
12
|
+
class ActiveRecordWrapper < Model
|
13
|
+
|
14
|
+
attr_reader :model, :timestamp_field
|
15
|
+
|
16
|
+
def initialize(model, options={})
|
17
|
+
@model = model
|
18
|
+
@timestamp_field = options.delete(:timestamp_field) || 'updated_at'
|
19
|
+
@limit = options.delete(:limit)
|
20
|
+
|
21
|
+
unless options.empty?
|
22
|
+
raise ArgumentError.new(
|
23
|
+
"Unsupported options [#{options.keys.join(', ')}]"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def earliest
|
29
|
+
model.find(:first,
|
30
|
+
:order => "#{timestamp_field} asc").send(timestamp_field)
|
31
|
+
end
|
32
|
+
|
33
|
+
def latest
|
34
|
+
model.find(:first,
|
35
|
+
:order => "#{timestamp_field} desc").send(timestamp_field)
|
36
|
+
end
|
37
|
+
# A model class is expected to provide a method Model.sets that
|
38
|
+
# returns all the sets the model supports. See the
|
39
|
+
# activerecord_provider tests for an example.
|
40
|
+
def sets
|
41
|
+
model.sets if model.respond_to?(:sets)
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(selector, options={})
|
45
|
+
return next_set(options[:resumption_token]) if options[:resumption_token]
|
46
|
+
conditions = sql_conditions(options)
|
47
|
+
if :all == selector
|
48
|
+
total = model.count(:id, :conditions => conditions)
|
49
|
+
if @limit && total > @limit
|
50
|
+
select_partial(ResumptionToken.new(options.merge({:last => 0})))
|
51
|
+
else
|
52
|
+
model.find(:all, :conditions => conditions)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
begin
|
56
|
+
model.find(selector, :conditions => conditions)
|
57
|
+
rescue ActiveRecord::RecordNotFound
|
58
|
+
raise OAI::IdException.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def deleted?(record)
|
64
|
+
if record.respond_to?(:deleted_at)
|
65
|
+
return record.deleted_at
|
66
|
+
elsif record.respond_to?(:deleted)
|
67
|
+
return record.deleted
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
# Request the next set in this sequence.
|
75
|
+
def next_set(token_string)
|
76
|
+
raise OAI::ResumptionTokenException.new unless @limit
|
77
|
+
|
78
|
+
token = ResumptionToken.parse(token_string)
|
79
|
+
total = model.count(:id, :conditions => token_conditions(token))
|
80
|
+
|
81
|
+
if @limit < total
|
82
|
+
select_partial(token)
|
83
|
+
else # end of result set
|
84
|
+
model.find(:all,
|
85
|
+
:conditions => token_conditions(token),
|
86
|
+
:limit => @limit, :order => "#{model.primary_key} asc")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# select a subset of the result set, and return it with a
|
91
|
+
# resumption token to get the next subset
|
92
|
+
def select_partial(token)
|
93
|
+
records = model.find(:all,
|
94
|
+
:conditions => token_conditions(token),
|
95
|
+
:limit => @limit,
|
96
|
+
:order => "#{model.primary_key} asc")
|
97
|
+
raise OAI::ResumptionTokenException.new unless records
|
98
|
+
offset = records.last.send(model.primary_key.to_sym)
|
99
|
+
|
100
|
+
PartialResult.new(records, token.next(offset))
|
101
|
+
end
|
102
|
+
|
103
|
+
# build a sql conditions statement from the content
|
104
|
+
# of a resumption token. It is very important not to
|
105
|
+
# miss any changes as records may change scope as the
|
106
|
+
# harvest is in progress. To avoid loosing any changes
|
107
|
+
# the last 'id' of the previous set is used as the
|
108
|
+
# filter to the next set.
|
109
|
+
def token_conditions(token)
|
110
|
+
last = token.last
|
111
|
+
sql = sql_conditions token.to_conditions_hash
|
112
|
+
|
113
|
+
return sql if 0 == last
|
114
|
+
# Now add last id constraint
|
115
|
+
sql[0] << " AND #{model.primary_key} > ?"
|
116
|
+
sql << last
|
117
|
+
|
118
|
+
return sql
|
119
|
+
end
|
120
|
+
|
121
|
+
# build a sql conditions statement from an OAI options hash
|
122
|
+
def sql_conditions(opts)
|
123
|
+
sql = []
|
124
|
+
sql << "#{timestamp_field} >= ?" << "#{timestamp_field} <= ?"
|
125
|
+
sql << "set = ?" if opts[:set]
|
126
|
+
esc_values = [sql.join(" AND ")]
|
127
|
+
esc_values << get_time(opts[:from]).localtime << get_time(opts[:until]).localtime #-- OAI 2.0 hack - UTC fix from record_responce
|
128
|
+
esc_values << opts[:set] if opts[:set]
|
129
|
+
|
130
|
+
return esc_values
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_time(time)
|
134
|
+
time.kind_of?(Time) ? time : Time.parse(time)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module OAI::Provider
|
2
|
+
# = OAI::Provider::Model
|
3
|
+
#
|
4
|
+
# Model implementers should subclass OAI::Provider::Model and override
|
5
|
+
# Model#earliest, Model#latest, and Model#find. Optionally Model#sets and
|
6
|
+
# Model#deleted? can be used to support sets and record deletions. It
|
7
|
+
# is also the responsibility of the model implementer to account for
|
8
|
+
# resumption tokens if support is required. Models that don't support
|
9
|
+
# resumption tokens should raise an exception if a limit is requested
|
10
|
+
# during initialization.
|
11
|
+
#
|
12
|
+
# earliest - should return the earliest update time in the repository.
|
13
|
+
# latest - should return the most recent update time in the repository.
|
14
|
+
# sets - should return an array of sets supported by the repository.
|
15
|
+
# deleted? - individual records returned should respond true or false
|
16
|
+
# when sent the deleted? message.
|
17
|
+
# available_formats - if overridden, individual records should return an
|
18
|
+
# array of prefixes for all formats in which that record is available,
|
19
|
+
# if other than ["oai_dc"]
|
20
|
+
#
|
21
|
+
# == Resumption Tokens
|
22
|
+
#
|
23
|
+
# For examples of using resumption tokens see the
|
24
|
+
# ActiveRecordWrapper, and ActiveRecordCachingWrapper classes.
|
25
|
+
#
|
26
|
+
# There are several helper models for dealing with resumption tokens please
|
27
|
+
# see the ResumptionToken class for more details.
|
28
|
+
#
|
29
|
+
|
30
|
+
class Model
|
31
|
+
attr_reader :timestamp_field
|
32
|
+
|
33
|
+
def initialize(limit = nil, timestamp_field = 'updated_at')
|
34
|
+
@limit = limit
|
35
|
+
@timestamp_field = timestamp_field
|
36
|
+
end
|
37
|
+
|
38
|
+
# should return the earliest timestamp available from this model.
|
39
|
+
def earliest
|
40
|
+
raise NotImplementedError.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# should return the latest timestamp available from this model.
|
44
|
+
def latest
|
45
|
+
raise NotImplementedError.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def sets
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# find is the core method of a model, it returns records from the model
|
53
|
+
# bases on the parameters passed in.
|
54
|
+
#
|
55
|
+
# <tt>selector</tt> can be a singular id, or the symbol :all
|
56
|
+
# <tt>options</tt> is a hash of options to be used to constrain the query.
|
57
|
+
#
|
58
|
+
# Valid options:
|
59
|
+
# * :from => earliest timestamp to be included in the results
|
60
|
+
# * :until => latest timestamp to be included in the results
|
61
|
+
# * :set => the set from which to retrieve the results
|
62
|
+
# * :metadata_prefix => type of metadata requested (this may be useful if
|
63
|
+
# not all records are available in all formats)
|
64
|
+
def find(selector, options={})
|
65
|
+
raise NotImplementedError.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def deleted?
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OAI::Provider
|
2
|
+
# = OAI::Provider::PartialResult
|
3
|
+
#
|
4
|
+
# PartialResult is used for returning a set/page of results from a model
|
5
|
+
# that supports resumption tokens. It should contain and array of
|
6
|
+
# records, and a resumption token for getting the next set/page.
|
7
|
+
#
|
8
|
+
class PartialResult
|
9
|
+
attr_reader :records, :token
|
10
|
+
|
11
|
+
def initialize(records, token = nil)
|
12
|
+
@records = records
|
13
|
+
@token = token
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
|
3
|
+
class GetRecord < RecordResponse
|
4
|
+
required_parameters :identifier, :metadata_prefix
|
5
|
+
|
6
|
+
def to_xml
|
7
|
+
id = extract_identifier(options.delete(:identifier))
|
8
|
+
unless record = provider.model.find(id, options)
|
9
|
+
raise OAI::IdException.new
|
10
|
+
end
|
11
|
+
|
12
|
+
response do |r|
|
13
|
+
r.GetRecord do
|
14
|
+
r.record do
|
15
|
+
header_for record
|
16
|
+
data_for record unless deleted?(record)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
|
3
|
+
class Identify < Base
|
4
|
+
|
5
|
+
def to_xml
|
6
|
+
response do |r|
|
7
|
+
r.Identify do
|
8
|
+
r.repositoryName provider.name
|
9
|
+
r.baseURL provider.url
|
10
|
+
r.protocolVersion 2.0
|
11
|
+
if provider.email and provider.email.respond_to?(:each)
|
12
|
+
provider.email.each { |address| r.adminEmail address }
|
13
|
+
else
|
14
|
+
r.adminEmail provider.email.to_s
|
15
|
+
end
|
16
|
+
r.earliestDatestamp Time.parse(provider.model.earliest.to_s).utc.xmlschema
|
17
|
+
r.deletedRecord provider.delete_support.to_s
|
18
|
+
r.granularity provider.granularity
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
|
3
|
+
class ListIdentifiers < RecordResponse
|
4
|
+
required_parameters :metadata_prefix
|
5
|
+
|
6
|
+
def to_xml
|
7
|
+
result = provider.model.find(:all, options)
|
8
|
+
|
9
|
+
# result may be an array of records, or a partial result
|
10
|
+
records = result.respond_to?(:records) ? result.records : result
|
11
|
+
|
12
|
+
raise OAI::NoMatchException.new if records.nil? or records.empty?
|
13
|
+
format = requested_format # not call this in each iteration
|
14
|
+
records.reject! do |r|
|
15
|
+
!record_supports(r, format)
|
16
|
+
end
|
17
|
+
|
18
|
+
response do |r|
|
19
|
+
r.ListIdentifiers do
|
20
|
+
records.each do |rec|
|
21
|
+
header_for rec
|
22
|
+
end
|
23
|
+
|
24
|
+
# append resumption token for getting next group of records
|
25
|
+
if result.respond_to?(:token)
|
26
|
+
r.target! << result.token.to_xml
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
class ListMetadataFormats < RecordResponse
|
3
|
+
valid_parameters :identifier
|
4
|
+
|
5
|
+
def to_xml
|
6
|
+
# Get a list of all the formats the provider understands.
|
7
|
+
formats = provider.formats.values
|
8
|
+
|
9
|
+
# if it's a doc-specific request
|
10
|
+
if options.include?(:identifier)
|
11
|
+
id = extract_identifier(options[:identifier])
|
12
|
+
unless record = provider.model.find(id, options)
|
13
|
+
raise OAI::IdException.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Remove any format that this particular record can't be provided in.
|
17
|
+
formats.reject! { |f| !record_supports(record, f.prefix) }
|
18
|
+
end
|
19
|
+
response do |r|
|
20
|
+
r.ListMetadataFormats do
|
21
|
+
formats.each do |format|
|
22
|
+
r.metadataFormat do
|
23
|
+
r.metadataPrefix format.prefix
|
24
|
+
r.schema format.schema
|
25
|
+
r.metadataNamespace format.namespace
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
|
3
|
+
class ListRecords < RecordResponse
|
4
|
+
required_parameters :metadata_prefix
|
5
|
+
|
6
|
+
def to_xml
|
7
|
+
result = provider.model.find(:all, options)
|
8
|
+
# result may be an array of records, or a partial result
|
9
|
+
records = result.respond_to?(:records) ? result.records : result
|
10
|
+
|
11
|
+
raise OAI::NoMatchException.new if records.nil? or records.empty?
|
12
|
+
|
13
|
+
response do |r|
|
14
|
+
r.ListRecords do
|
15
|
+
records.each do |rec|
|
16
|
+
r.record do
|
17
|
+
header_for rec
|
18
|
+
data_for rec unless deleted?(rec)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# append resumption token for getting next group of records
|
23
|
+
if result.respond_to?(:token)
|
24
|
+
r.target! << result.token.to_xml
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
|
3
|
+
class ListSets < Base
|
4
|
+
|
5
|
+
def to_xml
|
6
|
+
raise OAI::SetException.new unless provider.model.sets
|
7
|
+
|
8
|
+
response do |r|
|
9
|
+
r.ListSets do
|
10
|
+
provider.model.sets.each do |set|
|
11
|
+
r.set do
|
12
|
+
r.setSpec set.spec
|
13
|
+
r.setName set.name
|
14
|
+
r.setDescription(set.description) if set.respond_to?(:description)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module OAI::Provider::Response
|
2
|
+
class RecordResponse < Base
|
3
|
+
def self.inherited(klass)
|
4
|
+
klass.valid_parameters :metadata_prefix, :from, :until, :set
|
5
|
+
klass.default_parameters :from => Proc.new {|x| Time.parse(x.provider.model.earliest.to_s) },
|
6
|
+
:until => Proc.new {|x| Time.parse(x.provider.model.latest.to_s) }
|
7
|
+
end
|
8
|
+
|
9
|
+
# emit record header
|
10
|
+
def header_for(record)
|
11
|
+
param = Hash.new
|
12
|
+
param[:status] = 'deleted' if deleted?(record)
|
13
|
+
@builder.header param do
|
14
|
+
@builder.identifier identifier_for(record)
|
15
|
+
@builder.datestamp timestamp_for(record)
|
16
|
+
sets_for(record).each do |set|
|
17
|
+
@builder.setSpec set.spec
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# metadata - core routine for delivering metadata records
|
22
|
+
#
|
23
|
+
def data_for(record)
|
24
|
+
@builder.metadata do
|
25
|
+
@builder.target! << provider.format(requested_format).encode(provider.model, record)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def identifier_for(record)
|
32
|
+
"#{provider.prefix}/#{record.id}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def timestamp_for(record)
|
36
|
+
record.send(provider.model.timestamp_field).utc.xmlschema
|
37
|
+
end
|
38
|
+
|
39
|
+
def sets_for(record)
|
40
|
+
return [] unless record.respond_to?(:sets) and record.sets
|
41
|
+
record.sets.respond_to?(:each) ? record.sets : [record.sets]
|
42
|
+
end
|
43
|
+
|
44
|
+
def requested_format
|
45
|
+
format =
|
46
|
+
if options[:metadata_prefix]
|
47
|
+
options[:metadata_prefix]
|
48
|
+
elsif options[:resumption_token]
|
49
|
+
OAI::Provider::ResumptionToken.extract_format(options[:resumption_token])
|
50
|
+
end
|
51
|
+
raise OAI::FormatException.new unless provider.format_supported?(format)
|
52
|
+
|
53
|
+
format
|
54
|
+
end
|
55
|
+
|
56
|
+
def deleted?(record)
|
57
|
+
return record.deleted? if record.respond_to?(:deleted?)
|
58
|
+
return record.deleted if record.respond_to?(:deleted)
|
59
|
+
return record.deleted_at if record.respond_to?(:deleted_at)
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def record_supports(record, prefix)
|
64
|
+
prefix == 'oai_dc' or
|
65
|
+
record.respond_to?("to_#{prefix}") or
|
66
|
+
record.respond_to?("map_#{prefix}")
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|