amee 2.5.1 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|