@@ -8,16 +8,41 @@ module OAI::Provider
8
8
# The ResumptionToken class forms the basis of paging query results. It
9
9
# provides several helper methods for dealing with resumption tokens.
10
10
#
11
+ # OAI-PMH spec does not specify anything about resumptionToken format, they can
12
+ # be purely opaque tokens.
13
+ #
14
+ # Our implementation however encodes everything needed to construct the next page
15
+ # inside the resumption token.
16
+ #
17
+ # == The 'last' component: offset or ID/pk to resume from
18
+ #
19
+ # The `#last` component is an offset or ID to resume from. In the case of it being
20
+ # an ID to resume from, this assumes that ID's are sortable and results are returned
21
+ # in ID order, so that the 'last' ID can be used as the place to resume from.
22
+ #
23
+ # Originally it was assumed that #last was always an integer, but since existing
24
+ # implementations (like ActiveRecordWrapper) used it as an ID, and identifiers and
25
+ # primary keys are _not_ always integers (can be UUID etc), we have expanded to allow
26
+ # any string value.
27
+ #
28
+ # However, for backwards compatibility #last always returns an integer (sometimes 0 if
29
+ # actual last component is not an integer), and #last_str returns the full string version.
30
+ # Trying to change #last itself to be string broke a lot of existing code in this gem
31
+ # in mysterious ways.
32
+ #
33
+ # Also beware that in some cases the value 0/"0" seems to be a special value used
34
+ # to signify some special case. A lot of "code archeology" going on here after significant
35
+ # period of no maintenance to this gem.
11
36
class ResumptionToken
12
- attr_reader :prefix , :set , :from , :until , :last , :expiration , :total
37
+ attr_reader :prefix , :set , :from , :until , :last , :last_str , : expiration, :total
13
38
14
39
# parses a token string and returns a ResumptionToken
15
40
def self . parse ( token_string )
16
41
begin
17
42
options = { }
18
- matches = /(.+):(\d +)$/ . match ( token_string )
19
- options [ :last ] = matches . captures [ 1 ] . to_i
20
-
43
+ matches = /(.+):([^ :] +)$/ . match ( token_string )
44
+ options [ :last ] = matches . captures [ 1 ]
45
+
21
46
parts = matches . captures [ 0 ] . split ( '.' )
22
47
options [ :metadata_prefix ] = parts . shift
23
48
parts . each do |part |
@@ -35,7 +60,7 @@ def self.parse(token_string)
35
60
raise OAI ::ResumptionTokenException . new
36
61
end
37
62
end
38
-
63
+
39
64
# extracts the metadata prefix from a token string
40
65
def self . extract_format ( token_string )
41
66
return token_string . split ( '.' ) [ 0 ]
@@ -44,32 +69,32 @@ def self.extract_format(token_string)
44
69
def initialize ( options , expiration = nil , total = nil )
45
70
@prefix = options [ :metadata_prefix ]
46
71
@set = options [ :set ]
47
- @ last = options [ :last ]
72
+ self . last = options [ :last ]
48
73
@from = options [ :from ] if options [ :from ]
49
74
@until = options [ :until ] if options [ :until ]
50
75
@expiration = expiration if expiration
51
76
@total = total if total
52
77
end
53
-
78
+
54
79
# convenience method for setting the offset of the next set of results
55
80
def next ( last )
56
- @ last = last
81
+ self . last = last
57
82
self
58
83
end
59
-
84
+
60
85
def ==( other )
61
86
prefix == other . prefix and set == other . set and from == other . from and
62
- self . until == other . until and last == other . last and
87
+ self . until == other . until and last == other . last and
63
88
expiration == other . expiration and total == other . total
64
89
end
65
-
90
+
66
91
# output an xml resumption token
67
92
def to_xml
68
93
xml = Builder ::XmlMarkup . new
69
94
xml . resumptionToken ( encode_conditions , hash_of_attributes )
70
95
xml . target!
71
96
end
72
-
97
+
73
98
# return a hash containing just the model selection parameters
74
99
def to_conditions_hash
75
100
conditions = { :metadata_prefix => self . prefix }
@@ -78,20 +103,31 @@ def to_conditions_hash
78
103
conditions [ :until ] = self . until if self . until
79
104
conditions
80
105
end
81
-
82
- # return the a string representation of the token minus the offset
106
+
107
+ # return the a string representation of the token minus the offset/ID
108
+ #
109
+ # Q: Why does it eliminate the offset/id "last" on the end? Doesn't fully
110
+ # represent state without it, which is confusing. Not sure, but
111
+ # other code seems to rely on it, tests break if not.
83
112
def to_s
84
113
encode_conditions . gsub ( /:\w +?$/ , '' )
85
114
end
86
115
87
116
private
88
-
117
+
118
+ # take care of our logic to store an integer and a str version, for backwards
119
+ # compat where it was assumed to be an integer, as well as supporting string.
120
+ def last = ( value )
121
+ @last = value . to_i
122
+ @last_str = value . to_s
123
+ end
124
+
89
125
def encode_conditions
90
126
encoded_token = @prefix . to_s . dup
91
127
encoded_token << ".s(#{ set } )" if set
92
128
encoded_token << ".f(#{ self . from . utc . xmlschema } )" if self . from
93
129
encoded_token << ".u(#{ self . until . utc . xmlschema } )" if self . until
94
- encoded_token << ":#{ last } "
130
+ encoded_token << ":#{ last_str } "
95
131
end
96
132
97
133
def hash_of_attributes
0 commit comments