@@ -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,34 @@ 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
+ @last = options [ :last ] . to_i
73
+ @last_str = options [ :last ] . to_s
48
74
@from = options [ :from ] if options [ :from ]
49
75
@until = options [ :until ] if options [ :until ]
50
76
@expiration = expiration if expiration
51
77
@total = total if total
52
78
end
53
-
79
+
54
80
# convenience method for setting the offset of the next set of results
55
81
def next ( last )
56
- @last = last
82
+ @last_str = last . to_s
83
+ @last = last . to_i
57
84
self
58
85
end
59
-
86
+
60
87
def ==( other )
61
88
prefix == other . prefix and set == other . set and from == other . from and
62
- self . until == other . until and last == other . last and
89
+ self . until == other . until and last == other . last and
63
90
expiration == other . expiration and total == other . total
64
91
end
65
-
92
+
66
93
# output an xml resumption token
67
94
def to_xml
68
95
xml = Builder ::XmlMarkup . new
69
96
xml . resumptionToken ( encode_conditions , hash_of_attributes )
70
97
xml . target!
71
98
end
72
-
99
+
73
100
# return a hash containing just the model selection parameters
74
101
def to_conditions_hash
75
102
conditions = { :metadata_prefix => self . prefix }
@@ -78,20 +105,24 @@ def to_conditions_hash
78
105
conditions [ :until ] = self . until if self . until
79
106
conditions
80
107
end
81
-
82
- # return the a string representation of the token minus the offset
108
+
109
+ # return the a string representation of the token minus the offset/ID
110
+ #
111
+ # Q: Why does it eliminate the offset/id "last" on the end? Doesn't fully
112
+ # represent state without it, which is confusing. Not sure, but
113
+ # other code seems to rely on it, tests break if not.
83
114
def to_s
84
115
encode_conditions . gsub ( /:\w +?$/ , '' )
85
116
end
86
117
87
118
private
88
-
119
+
89
120
def encode_conditions
90
121
encoded_token = @prefix . to_s . dup
91
122
encoded_token << ".s(#{ set } )" if set
92
123
encoded_token << ".f(#{ self . from . utc . xmlschema } )" if self . from
93
124
encoded_token << ".u(#{ self . until . utc . xmlschema } )" if self . until
94
- encoded_token << ":#{ last } "
125
+ encoded_token << ":#{ last_str } "
95
126
end
96
127
97
128
def hash_of_attributes
0 commit comments