amee 2.5.1 → 2.6.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.
- data/CHANGELOG +13 -4
- data/README +11 -0
- data/lib/amee/collection.rb +25 -10
- data/lib/amee/connection.rb +28 -12
- data/lib/amee/data_category.rb +63 -67
- data/lib/amee/data_item.rb +89 -84
- data/lib/amee/exceptions.rb +28 -1
- data/lib/amee/object.rb +32 -1
- data/lib/amee/parse_helper.rb +2 -2
- data/lib/amee/rails.rb +6 -0
- data/lib/amee/version.rb +2 -2
- metadata +5 -5
data/CHANGELOG
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
= Changelog
|
2
2
|
|
3
|
+
== 2.6.0
|
4
|
+
* Add auto-retry for certain HTTP failures.
|
5
|
+
* Add options to Connection.new for timeout and retry.
|
6
|
+
* Add options to amee.yml for timeout and retry.
|
7
|
+
* More useful exception reporting in event of failures.
|
8
|
+
|
9
|
+
== 2.5.1
|
10
|
+
* Fix for bug in empty tag parsing
|
11
|
+
|
3
12
|
== 2.5.0
|
4
13
|
* Use ActiveSupport::Cache for caching
|
5
14
|
* Add ability to specify cache and debug options in amee.yml for Rails projects.
|
@@ -17,10 +26,10 @@
|
|
17
26
|
|
18
27
|
== 2.2.0
|
19
28
|
|
20
|
-
* Add SSL support. SSL connections are now used by default.
|
21
|
-
* Log4r support
|
22
|
-
* Support for reading ItemValueDefinitions
|
29
|
+
* Add SSL support. SSL connections are now used by default.
|
30
|
+
* Log4r support
|
31
|
+
* Support for reading ItemValueDefinitions
|
23
32
|
* Include accessors from other objects
|
24
|
-
* Internal improvements including
|
33
|
+
* Internal improvements including
|
25
34
|
* Improved paging support
|
26
35
|
* Tidier code for collections
|
data/README
CHANGED
@@ -107,6 +107,17 @@ To enable caching, pass ':cache => :memory_store' to AMEE::Connection.new, or ad
|
|
107
107
|
same caching configuration as your Rails app, you can add 'cache: rails' to
|
108
108
|
amee.yml instead. Caching is disabled by default.
|
109
109
|
|
110
|
+
== RETRY / TIMEOUT SUPPORT
|
111
|
+
|
112
|
+
The AMEE::Connection object can now automatically retry if a request fails due to
|
113
|
+
network problems or connection failures. To enable this feature, pass ':retries => 3'
|
114
|
+
to AMEE::Connection.new, or add 'retries: 3' to your amee.yml if using Rails. You can
|
115
|
+
change the number of retry attempts, 3 is just used as an example above.
|
116
|
+
|
117
|
+
The Connection object also allows a timeout to be set for requests. By default this is
|
118
|
+
set to 60 seconds, but if you want to provide a different value (30 seconds for
|
119
|
+
instance), pass ':timeout => 30' to AMEE::Connection.new, or 'timeout: 30' in amee.yml.
|
120
|
+
|
110
121
|
== UPGRADING TO VERSION > 2
|
111
122
|
|
112
123
|
There are a few changes to the API exposed by this gem for version 2. The main
|
data/lib/amee/collection.rb
CHANGED
@@ -12,10 +12,14 @@ module AMEE
|
|
12
12
|
each_page do
|
13
13
|
parse_page
|
14
14
|
end
|
15
|
-
rescue
|
16
|
-
|
17
|
-
raise AMEE::BadData.new("Couldn't load #{self.class.name}.\n#{response}")
|
15
|
+
rescue JSONParseError, XMLParseError
|
16
|
+
@connection.expire(collectionpath)
|
17
|
+
raise AMEE::BadData.new("Couldn't load #{self.class.name}.\n#{@response}")
|
18
|
+
rescue AMEE::BadData
|
19
|
+
@connection.expire(collectionpath)
|
20
|
+
raise
|
18
21
|
end
|
22
|
+
|
19
23
|
def parse_page
|
20
24
|
if json
|
21
25
|
jsoncollector.each do |p|
|
@@ -26,7 +30,7 @@ module AMEE
|
|
26
30
|
end
|
27
31
|
else
|
28
32
|
doc.xpath(xmlcollectorpath.split('/')[1...-1].join('/')).first or
|
29
|
-
raise AMEE::BadData.new("Couldn't load #{self.class.name}.
|
33
|
+
raise AMEE::BadData.new("Couldn't load #{self.class.name}. \n#{@response}")
|
30
34
|
doc.xpath(xmlcollectorpath).each do |p|
|
31
35
|
obj=klass.new(parse_xml(p))
|
32
36
|
obj.connection = connection
|
@@ -39,12 +43,23 @@ module AMEE
|
|
39
43
|
|
40
44
|
def fetch
|
41
45
|
@options.merge! @pager.options if @pager
|
42
|
-
|
43
|
-
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
retries = [1] * connection.retries
|
47
|
+
begin
|
48
|
+
@response= @connection.get(collectionpath, @options).body
|
49
|
+
if @response.is_json?
|
50
|
+
@json = true
|
51
|
+
@doc = JSON.parse(@response)
|
52
|
+
else
|
53
|
+
@doc = load_xml_doc(@response)
|
54
|
+
end
|
55
|
+
rescue JSON::ParserError, Nokogiri::XML::SyntaxError
|
56
|
+
@connection.expire(collectionpath)
|
57
|
+
if delay = retries.shift
|
58
|
+
sleep delay
|
59
|
+
retry
|
60
|
+
else
|
61
|
+
raise
|
62
|
+
end
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
data/lib/amee/connection.rb
CHANGED
@@ -18,6 +18,7 @@ module AMEE
|
|
18
18
|
@auth_token = nil
|
19
19
|
@format = options[:format] || (defined?(JSON) ? :json : :xml)
|
20
20
|
@amee_source = options[:amee_source]
|
21
|
+
@retries = options[:retries] || 0
|
21
22
|
if !valid?
|
22
23
|
raise "You must supply connection details - server, username and password are all required!"
|
23
24
|
end
|
@@ -52,7 +53,7 @@ module AMEE
|
|
52
53
|
@http.verify_depth = 5
|
53
54
|
end
|
54
55
|
end
|
55
|
-
|
56
|
+
self.timeout = options[:timeout] || 60
|
56
57
|
@http.set_debug_output($stdout) if options[:enable_debug]
|
57
58
|
@debug = options[:enable_debug]
|
58
59
|
end
|
@@ -61,13 +62,14 @@ module AMEE
|
|
61
62
|
attr_reader :server
|
62
63
|
attr_reader :username
|
63
64
|
attr_reader :password
|
65
|
+
attr_reader :retries
|
64
66
|
|
65
67
|
def timeout
|
66
68
|
@http.read_timeout
|
67
69
|
end
|
68
70
|
|
69
71
|
def timeout=(t)
|
70
|
-
@http.read_timeout = t
|
72
|
+
@http.open_timeout = @http.read_timeout = t
|
71
73
|
end
|
72
74
|
|
73
75
|
def version
|
@@ -217,20 +219,22 @@ module AMEE
|
|
217
219
|
if response.body.include? "would have resulted in a duplicate resource being created"
|
218
220
|
raise AMEE::DuplicateResource.new("The specified resource already exists. This is most often caused by creating an item that overlaps another in time.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
|
219
221
|
else
|
220
|
-
raise AMEE::
|
222
|
+
raise AMEE::BadRequest.new("Bad request. This is probably due to malformed input data.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
|
221
223
|
end
|
222
|
-
else
|
223
|
-
raise AMEE::UnknownError.new("An error occurred while talking to AMEE: HTTP response code #{response.code}.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
|
224
224
|
end
|
225
|
+
raise AMEE::UnknownError.new("An error occurred while talking to AMEE: HTTP response code #{response.code}.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
|
225
226
|
end
|
226
227
|
|
227
228
|
def do_request(request, format = @format)
|
228
229
|
# Open HTTP connection
|
229
230
|
@http.start
|
230
|
-
# Do request
|
231
231
|
begin
|
232
|
+
# Set auth token in cookie (and header just in case someone's stripping cookies)
|
233
|
+
request['Cookie'] = "authToken=#{@auth_token}"
|
234
|
+
request['authToken'] = @auth_token
|
235
|
+
# Do request
|
232
236
|
timethen=Time.now
|
233
|
-
response = send_request(request, format)
|
237
|
+
response = send_request(@http, request, format)
|
234
238
|
Logger.log.debug("Requesting #{request.class} at #{request.path} with #{request.body} in format #{format}, taking #{(Time.now-timethen)*1000} miliseconds")
|
235
239
|
end while !response_ok?(response, request)
|
236
240
|
# Return response
|
@@ -242,16 +246,28 @@ module AMEE
|
|
242
246
|
@http.finish if @http.started?
|
243
247
|
end
|
244
248
|
|
245
|
-
def send_request(request, format = @format)
|
246
|
-
# Set auth token in cookie (and header just in case someone's stripping cookies)
|
247
|
-
request['Cookie'] = "authToken=#{@auth_token}"
|
248
|
-
request['authToken'] = @auth_token
|
249
|
+
def send_request(connection, request, format = @format)
|
249
250
|
# Set accept header
|
250
251
|
request['Accept'] = content_type(format)
|
251
252
|
# Set AMEE source header if set
|
252
253
|
request['X-AMEE-Source'] = @amee_source if @amee_source
|
253
254
|
# Do the business
|
254
|
-
|
255
|
+
retries = [1] * @retries
|
256
|
+
begin
|
257
|
+
response = connection.request(request)
|
258
|
+
# 500-series errors fail early
|
259
|
+
if ['502', '503', '504'].include? response.code
|
260
|
+
raise AMEE::ConnectionFailed.new("A connection error occurred while talking to AMEE: HTTP response code #{response.code}.\nRequest: #{request.method} #{request.path}")
|
261
|
+
end
|
262
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
263
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, AMEE::ConnectionFailed => e
|
264
|
+
if delay = retries.shift
|
265
|
+
sleep delay
|
266
|
+
retry
|
267
|
+
else
|
268
|
+
raise
|
269
|
+
end
|
270
|
+
end
|
255
271
|
# Done
|
256
272
|
response
|
257
273
|
end
|
data/lib/amee/data_category.rb
CHANGED
@@ -25,37 +25,39 @@ module AMEE
|
|
25
25
|
def self.from_json(json)
|
26
26
|
# Parse json
|
27
27
|
doc = JSON.parse(json)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
doc['children']['dataItems']['rows']
|
48
|
-
|
49
|
-
|
50
|
-
|
28
|
+
begin
|
29
|
+
data = {}
|
30
|
+
data[:uid] = doc['dataCategory']['uid']
|
31
|
+
data[:created] = DateTime.parse(doc['dataCategory']['created'])
|
32
|
+
data[:modified] = DateTime.parse(doc['dataCategory']['modified'])
|
33
|
+
data[:name] = doc['dataCategory']['name']
|
34
|
+
data[:path] = doc['path']
|
35
|
+
data[:children] = []
|
36
|
+
data[:pager] = AMEE::Pager.from_json(doc['children']['pager'])
|
37
|
+
itemdef=doc['dataCategory']['itemDefinition']
|
38
|
+
data[:itemdef] = itemdef ? itemdef['uid'] : nil
|
39
|
+
doc['children']['dataCategories'].each do |child|
|
40
|
+
category_data = {}
|
41
|
+
category_data[:name] = child['name']
|
42
|
+
category_data[:path] = child['path']
|
43
|
+
category_data[:uid] = child['uid']
|
44
|
+
data[:children] << category_data
|
45
|
+
end
|
46
|
+
data[:items] = []
|
47
|
+
if doc['children']['dataItems']['rows']
|
48
|
+
doc['children']['dataItems']['rows'].each do |item|
|
49
|
+
item_data = {}
|
50
|
+
item.each_pair do |key, value|
|
51
|
+
item_data[key.to_sym] = value
|
52
|
+
end
|
53
|
+
data[:items] << item_data
|
51
54
|
end
|
52
|
-
data[:items] << item_data
|
53
55
|
end
|
56
|
+
# Create object
|
57
|
+
Category.new(data)
|
58
|
+
rescue AMEE::JSONParseError
|
59
|
+
raise AMEE::BadData.new("Couldn't load DataCategory from JSON data. Check that your URL is correct.\n#{json}")
|
54
60
|
end
|
55
|
-
# Create object
|
56
|
-
Category.new(data)
|
57
|
-
rescue
|
58
|
-
raise AMEE::BadData.new("Couldn't load DataCategory from JSON data. Check that your URL is correct.\n#{json}")
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.xmlpathpreamble
|
@@ -64,39 +66,41 @@ module AMEE
|
|
64
66
|
def self.from_xml(xml)
|
65
67
|
# Parse XML
|
66
68
|
@doc = doc= REXML::Document.new(xml)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
data[:items] = []
|
85
|
-
REXML::XPath.each(doc, '/Resources/DataCategoryResource//Children/DataItems/DataItem') do |item|
|
86
|
-
item_data = {}
|
87
|
-
item_data[:uid] = item.attributes['uid'].to_s
|
88
|
-
item.elements.each do |element|
|
89
|
-
item_data[element.name.to_sym] = element.text
|
69
|
+
begin
|
70
|
+
data = {}
|
71
|
+
data[:uid] = x '@uid'
|
72
|
+
data[:created] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataCategoryResource/DataCategory/@created").to_s)
|
73
|
+
data[:modified] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataCategoryResource/DataCategory/@modified").to_s)
|
74
|
+
data[:name] = (REXML::XPath.first(doc, '/Resources/DataCategoryResource/DataCategory/Name') || REXML::XPath.first(doc, '/Resources/DataCategoryResource/DataCategory/name')).text
|
75
|
+
data[:path] = (REXML::XPath.first(doc, '/Resources/DataCategoryResource/Path') || REXML::XPath.first(doc, '/Resources/DataCategoryResource/DataCategory/path')).text || ""
|
76
|
+
data[:pager] = AMEE::Pager.from_xml(REXML::XPath.first(doc, '//Pager'))
|
77
|
+
itemdefattrib=REXML::XPath.first(doc, '/Resources/DataCategoryResource//ItemDefinition/@uid')
|
78
|
+
data[:itemdef] = itemdefattrib ? itemdefattrib.to_s : nil
|
79
|
+
data[:children] = []
|
80
|
+
REXML::XPath.each(doc, '/Resources/DataCategoryResource//Children/DataCategories/DataCategory') do |child|
|
81
|
+
category_data = {}
|
82
|
+
category_data[:name] = (child.elements['Name'] || child.elements['name']).text
|
83
|
+
category_data[:path] = (child.elements['Path'] || child.elements['path']).text
|
84
|
+
category_data[:uid] = child.attributes['uid'].to_s
|
85
|
+
data[:children] << category_data
|
90
86
|
end
|
91
|
-
|
92
|
-
|
87
|
+
data[:items] = []
|
88
|
+
REXML::XPath.each(doc, '/Resources/DataCategoryResource//Children/DataItems/DataItem') do |item|
|
89
|
+
item_data = {}
|
90
|
+
item_data[:uid] = item.attributes['uid'].to_s
|
91
|
+
item.elements.each do |element|
|
92
|
+
item_data[element.name.to_sym] = element.text
|
93
|
+
end
|
94
|
+
if item_data[:path].nil?
|
95
|
+
item_data[:path] = item_data[:uid]
|
96
|
+
end
|
97
|
+
data[:items] << item_data
|
93
98
|
end
|
94
|
-
|
99
|
+
# Create object
|
100
|
+
Category.new(data)
|
101
|
+
rescue AMEE::XMLParseError
|
102
|
+
raise AMEE::BadData.new("Couldn't load DataCategory from XML data. Check that your URL is correct.\n#{xml}")
|
95
103
|
end
|
96
|
-
# Create object
|
97
|
-
Category.new(data)
|
98
|
-
rescue
|
99
|
-
raise AMEE::BadData.new("Couldn't load DataCategory from XML data. Check that your URL is correct.\n#{xml}")
|
100
104
|
end
|
101
105
|
|
102
106
|
def self.get(connection, path, orig_options = {})
|
@@ -112,20 +116,12 @@ module AMEE
|
|
112
116
|
end
|
113
117
|
|
114
118
|
# Load data from path
|
115
|
-
|
119
|
+
cat = get_and_parse(connection, path, options)
|
116
120
|
|
117
|
-
# Parse data from response
|
118
|
-
if response.is_json?
|
119
|
-
cat = Category.from_json(response)
|
120
|
-
else
|
121
|
-
cat = Category.from_xml(response)
|
122
|
-
end
|
123
121
|
# Store connection in object for future use
|
124
122
|
cat.connection = connection
|
125
123
|
# Done
|
126
124
|
return cat
|
127
|
-
rescue
|
128
|
-
raise AMEE::BadData.new("Couldn't load DataCategory. Check that your URL is correct.\n#{response}")
|
129
125
|
end
|
130
126
|
|
131
127
|
def self.root(connection)
|
data/lib/amee/data_item.rb
CHANGED
@@ -34,102 +34,107 @@ module AMEE
|
|
34
34
|
def self.from_json(json)
|
35
35
|
# Read JSON
|
36
36
|
doc = JSON.parse(json)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
data[:total_amount]
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
37
|
+
begin
|
38
|
+
data = {}
|
39
|
+
data[:uid] = doc['dataItem']['uid']
|
40
|
+
data[:created] = DateTime.parse(doc['dataItem']['created'])
|
41
|
+
data[:modified] = DateTime.parse(doc['dataItem']['modified'])
|
42
|
+
data[:name] = doc['dataItem']['name']
|
43
|
+
data[:path] = doc['path']
|
44
|
+
data[:label] = doc['dataItem']['label']
|
45
|
+
data[:item_definition] = doc['dataItem']['itemDefinition']['uid']
|
46
|
+
data[:category_uid] = doc['dataItem']['dataCategory']['uid']
|
47
|
+
# Read v2 total
|
48
|
+
data[:total_amount] = doc['amount']['value'] rescue nil
|
49
|
+
data[:total_amount_unit] = doc['amount']['unit'] rescue nil
|
50
|
+
# Read v1 total
|
51
|
+
if data[:total_amount].nil?
|
52
|
+
data[:total_amount] = doc['amountPerMonth'] rescue nil
|
53
|
+
data[:total_amount_unit] = "kg/month"
|
54
|
+
end
|
55
|
+
# Get values
|
56
|
+
data[:values] = []
|
57
|
+
doc['dataItem']['itemValues'].each do |value|
|
58
|
+
value_data = {}
|
59
|
+
value_data[:name] = value['name']
|
60
|
+
value_data[:path] = value['path']
|
61
|
+
value_data[:value] = value['value']
|
62
|
+
value_data[:drill] = value['itemValueDefinition']['drillDown'] rescue nil
|
63
|
+
value_data[:uid] = value['uid']
|
64
|
+
data[:values] << value_data
|
65
|
+
end
|
66
|
+
# Get choices
|
67
|
+
data[:choices] = []
|
68
|
+
doc['userValueChoices']['choices'].each do |choice|
|
69
|
+
choice_data = {}
|
70
|
+
choice_data[:name] = choice['name']
|
71
|
+
choice_data[:value] = choice['value']
|
72
|
+
data[:choices] << choice_data
|
73
|
+
end
|
74
|
+
data[:start_date] = DateTime.parse(doc['dataItem']['startDate']) rescue nil
|
75
|
+
# Create object
|
76
|
+
Item.new(data)
|
77
|
+
rescue
|
78
|
+
raise AMEE::BadData.new("Couldn't load DataItem from JSON. Check that your URL is correct.\n#{json}")
|
72
79
|
end
|
73
|
-
data[:start_date] = DateTime.parse(doc['dataItem']['startDate']) rescue nil
|
74
|
-
# Create object
|
75
|
-
Item.new(data)
|
76
|
-
rescue
|
77
|
-
raise AMEE::BadData.new("Couldn't load DataItem from JSON. Check that your URL is correct.\n#{json}")
|
78
80
|
end
|
79
81
|
|
80
82
|
def self.from_xml(xml)
|
81
83
|
# Parse data from response into hash
|
82
84
|
doc = REXML::Document.new(xml)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
data[:total_amount]
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
85
|
+
begin
|
86
|
+
data = {}
|
87
|
+
data[:uid] = REXML::XPath.first(doc, "/Resources/DataItemResource/DataItem/@uid").to_s
|
88
|
+
data[:created] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemResource/DataItem/@created").to_s)
|
89
|
+
data[:modified] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemResource/DataItem/@modified").to_s)
|
90
|
+
data[:name] = (REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/Name') || REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/name')).text
|
91
|
+
data[:path] = (REXML::XPath.first(doc, '/Resources/DataItemResource/Path') || REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/path')).text
|
92
|
+
data[:label] = (REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/Label') || REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/label')).text
|
93
|
+
data[:item_definition] = REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/ItemDefinition/@uid').to_s
|
94
|
+
data[:category_uid] = REXML::XPath.first(doc, '/Resources/DataItemResource/DataItem/DataCategory/@uid').to_s
|
95
|
+
# Read v2 total
|
96
|
+
data[:total_amount] = REXML::XPath.first(doc, '/Resources/DataItemResource/Amount').text.to_f rescue nil
|
97
|
+
data[:total_amount_unit] = REXML::XPath.first(doc, '/Resources/DataItemResource/Amount/@unit').to_s rescue nil
|
98
|
+
# Read v1 total
|
99
|
+
if data[:total_amount].nil?
|
100
|
+
data[:total_amount] = REXML::XPath.first(doc, '/Resources/DataItemResource/AmountPerMonth').text.to_f rescue nil
|
101
|
+
data[:total_amount_unit] = "kg/month"
|
102
|
+
end
|
103
|
+
# Get values
|
104
|
+
data[:values] = []
|
105
|
+
REXML::XPath.each(doc, '/Resources/DataItemResource/DataItem/ItemValues/ItemValue') do |value|
|
106
|
+
value_data = {}
|
107
|
+
value_data[:name] = (value.elements['Name'] || value.elements['name']).text
|
108
|
+
value_data[:path] = (value.elements['Path'] || value.elements['path']).text
|
109
|
+
value_data[:value] = (value.elements['Value'] || value.elements['value']).text
|
110
|
+
value_data[:drill] = value.elements['ItemValueDefinition'].elements['DrillDown'].text == "false" ? false : true rescue nil
|
111
|
+
value_data[:uid] = value.attributes['uid'].to_s
|
112
|
+
data[:values] << value_data
|
113
|
+
end
|
114
|
+
# Get choices
|
115
|
+
data[:choices] = []
|
116
|
+
REXML::XPath.each(doc, '/Resources/DataItemResource/Choices/Choices/Choice') do |choice|
|
117
|
+
choice_data = {}
|
118
|
+
choice_data[:name] = (choice.elements['Name']).text
|
119
|
+
choice_data[:value] = (choice.elements['Value']).text || ""
|
120
|
+
data[:choices] << choice_data
|
121
|
+
end
|
122
|
+
data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemResource/DataItem/StartDate").text) rescue nil
|
123
|
+
# Create object
|
124
|
+
Item.new(data)
|
125
|
+
rescue
|
126
|
+
raise AMEE::BadData.new("Couldn't load DataItem from XML. Check that your URL is correct.\n#{xml}")
|
118
127
|
end
|
119
|
-
data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemResource/DataItem/StartDate").text) rescue nil
|
120
|
-
# Create object
|
121
|
-
Item.new(data)
|
122
|
-
rescue
|
123
|
-
raise AMEE::BadData.new("Couldn't load DataItem from XML. Check that your URL is correct.\n#{xml}")
|
124
128
|
end
|
125
129
|
|
126
130
|
|
127
131
|
def self.get(connection, path, options = {})
|
128
132
|
# Load data from path
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
+
item= get_and_parse(connection, path, options)
|
134
|
+
# Store connection in object for future use
|
135
|
+
item.connection = connection
|
136
|
+
# Done
|
137
|
+
return item
|
133
138
|
end
|
134
139
|
|
135
140
|
def self.parse(connection, response)
|
data/lib/amee/exceptions.rb
CHANGED
@@ -18,6 +18,9 @@ module AMEE
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class BadRequest < Exception
|
22
|
+
end
|
23
|
+
|
21
24
|
class AuthFailed < Exception
|
22
25
|
end
|
23
26
|
|
@@ -42,4 +45,28 @@ module AMEE
|
|
42
45
|
class Deprecated < Exception
|
43
46
|
end
|
44
47
|
|
45
|
-
|
48
|
+
# These are used to classify exceptions that can occur during parsing
|
49
|
+
|
50
|
+
module XMLParseError
|
51
|
+
end
|
52
|
+
|
53
|
+
module JSONParseError
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set up possible XML parse errors
|
59
|
+
[
|
60
|
+
ArgumentError, # thrown by Date.parse
|
61
|
+
].each do |m|
|
62
|
+
m.send(:include, AMEE::XMLParseError)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set up possible JSON parse errors
|
66
|
+
[
|
67
|
+
ArgumentError, # thrown by Date.parse,
|
68
|
+
NoMethodError, # missing elements in JSON, thrown by nil[]
|
69
|
+
].each do |m|
|
70
|
+
m.send(:include, AMEE::JSONParseError)
|
71
|
+
end
|
72
|
+
|
data/lib/amee/object.rb
CHANGED
@@ -21,6 +21,37 @@ module AMEE
|
|
21
21
|
def expire_cache
|
22
22
|
@connection.expire_matching(full_path+'.*')
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
|
+
# A nice shared get/parse handler that handles retry on parse errors
|
26
|
+
def self.get_and_parse(connection, path, options)
|
27
|
+
# Note that we don't check the number of times retry has been done lower down
|
28
|
+
# and count separately instead.
|
29
|
+
# Worst case could be retries squared given the right pattern of failure, but
|
30
|
+
# that seems unlikely. Would need, for instance, repeated 503 503 200->parse_fail
|
31
|
+
retries = [1] * connection.retries
|
32
|
+
begin
|
33
|
+
# Load data from path
|
34
|
+
response = connection.get(path, options).body
|
35
|
+
# Parse data from response
|
36
|
+
if response.is_json?
|
37
|
+
from_json(response)
|
38
|
+
else
|
39
|
+
from_xml(response)
|
40
|
+
end
|
41
|
+
rescue JSON::ParserError, Nokogiri::XML::SyntaxError, REXML::ParseException => e
|
42
|
+
# Invalid JSON or XML received, try the GET again in case it got cut off mid-stream
|
43
|
+
connection.expire(path)
|
44
|
+
if delay = retries.shift
|
45
|
+
sleep delay
|
46
|
+
retry
|
47
|
+
else
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
rescue AMEE::BadData
|
51
|
+
connection.expire(path)
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
25
56
|
end
|
26
57
|
end
|
data/lib/amee/parse_helper.rb
CHANGED
data/lib/amee/rails.rb
CHANGED
@@ -12,6 +12,12 @@ module AMEE
|
|
12
12
|
if $AMEE_CONFIG['ssl'] == false
|
13
13
|
options.merge! :ssl => false
|
14
14
|
end
|
15
|
+
if $AMEE_CONFIG['retries']
|
16
|
+
options.merge! :retries => $AMEE_CONFIG['retries'].to_i
|
17
|
+
end
|
18
|
+
if $AMEE_CONFIG['timeout']
|
19
|
+
options.merge! :timeout => $AMEE_CONFIG['timeout'].to_i
|
20
|
+
end
|
15
21
|
if $AMEE_CONFIG['cache'] == 'rails'
|
16
22
|
# Pass in the rails cache store
|
17
23
|
options[:cache_store] = ActionController::Base.cache_store
|
data/lib/amee/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 2.6.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- James Smith
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-02-16 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|