gom-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47c079f7884f7690f09f7ce8c4c0a50bfc65e38e
4
+ data.tar.gz: 6231c9a90c47c9fa3f791b350e99e3219bbcd018
5
+ SHA512:
6
+ metadata.gz: e0c2f0fefd4cd006b1dad046c576ba8cd1e1720a763a502c062c8629ff18ab42ef30ef26ab019ca7e81bfc102333b36560eaa5af6adc1ad4f558f8eb474e064e
7
+ data.tar.gz: 63e505910da3384fca83820a086ac192de61280f1e1f3a397941bcbf5aa02522a6221a6fd938cff1e0011a9b9704abbf70ce074affa7037a1d8d1f8d6b571017
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ ## PROJECT::SPECIFIC
21
+ README.html
22
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ before_script:
4
+ script: "bundle exec rspec"
5
+ env:
6
+ - CI=true
7
+ rvm:
8
+ - 1.9.3
9
+ - 2.0.0
10
+ gemfile:
11
+ - Gemfile
12
+ notifications:
13
+ recipients:
14
+ - dirk.luesebrink@artcom.com
15
+ branches:
16
+ only:
17
+ - webmock
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # Guardfile info at: https://github.com/guard/guard#readme
2
+
3
+ guard 'rspec', all_on_start: true, cli: '--format nested --debug --color' do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
6
+ watch('spec/spec_helper.rb') { "spec" }
7
+ watch('lib/gom/client.rb') { "spec" }
8
+ end
data/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # Ruby Gom Client
2
+
3
+ ## Requirements
4
+
5
+ * Ruby 1.9
6
+
7
+ ## Setup and Initialization
8
+
9
+ ```ruby
10
+ GOM = Gom::Client.new GOM_ROOT
11
+ ```
12
+
13
+ Where the GOM_ROOT is of format `"http://<ip | hostname>[:<port>]"`. Further
14
+ operations are then performed via the initialized object (in this example
15
+ `GOM`)
16
+
17
+ ## RESTful operations
18
+
19
+ ### GET/retrieve
20
+
21
+ Attribute retrieval:
22
+
23
+ ```ruby
24
+ >>> myAttribute = GOM.retrieve("/test:myAttr")
25
+ {'attribute': {'ctime': '2012-10-12T08:46:48+02:00',
26
+ 'mtime': '2012-10-12T08:46:48+02:00',
27
+ 'name': 'myAttr',
28
+ 'node': '/test',
29
+ 'type': 'string',
30
+ 'value': 'test'}}
31
+ ```
32
+
33
+ Node retrieval:
34
+
35
+ ```ruby
36
+ >>> myNode = GOM.retrieve("/areas")
37
+ {'node': {'ctime': '2012-09-20T04:51:56+02:00',
38
+ 'entries': [{'ctime': '2012-07-30T16:13:02+02:00',
39
+ 'mtime': '2012-07-30T16:13:02+02:00',
40
+ 'node': '/areas/home'},
41
+ {'ctime': '2012-09-29T17:51:47+02:00',
42
+ 'mtime': '2012-09-29T17:51:47+02:00',
43
+ 'node': '/areas/life'},
44
+ {'ctime': '2012-06-26T21:13:35+02:00',
45
+ 'mtime': '2012-06-26T21:13:35+02:00',
46
+ 'node': '/areas/mobile'},
47
+ {'ctime': '2012-10-10T18:30:50+02:00',
48
+ 'mtime': '2012-10-10T18:30:50+02:00',
49
+ 'node': '/areas/move'},
50
+ {'ctime': '2012-09-20T02:19:30+02:00',
51
+ 'mtime': '2012-09-20T02:19:30+02:00',
52
+ 'node': '/areas/pre-show'},
53
+ {'ctime': '2012-07-30T14:03:57+02:00',
54
+ 'mtime': '2012-07-30T14:03:57+02:00',
55
+ 'node': '/areas/welcome'},
56
+ {'attribute': {'ctime': '2012-10-11T07:02:18+02:00',
57
+ 'mtime': '2012-10-11T07:02:18+02:00',
58
+ 'name': 'operational_mode',
59
+ 'node': '/areas',
60
+ 'type': 'string',
61
+ 'value': 'idle'}}],
62
+ 'mtime': '2012-09-20T04:51:56+02:00',
63
+ 'uri': '/areas'}}
64
+ ```
65
+
66
+ Retrieval of non-existent Node/Attribute:
67
+
68
+ ```ruby
69
+ >>> pprint(GOM.retrieve("/test/does-not-exist"))
70
+ None
71
+ >>> pprint(GOM.retrieve("/test:does-not-exist"))
72
+ None
73
+ ```
74
+
75
+ ### PUT/update
76
+
77
+ Attribute update
78
+
79
+ ```ruby
80
+ >>> GOM.update!("/test:temperature", "50 °C")
81
+ '50 °C'
82
+ ```
83
+
84
+ Node update
85
+
86
+ ```ruby
87
+ >>> GOM.update!("/test/weather", {"temperature": "50 °C", "wind_velocity": "3 km/h", "wind_direction": "NNW"})
88
+ {'status': 201}
89
+ ```
90
+
91
+ ### DELETE/destroy
92
+
93
+ Delete existing node
94
+
95
+ ```ruby
96
+ >>> GOM.destroy("/test/c18bf546-e577-414a-92d2-2ebdfb69b4f6")
97
+ True
98
+ ```
99
+
100
+ Delete non-existing node
101
+
102
+ ```ruby
103
+ >>> print(GOM.destroy("/test/does-not-exist"))
104
+ None
105
+ ```
106
+
107
+ Attributes are deleted accordingly
108
+
109
+ ### POST/create
110
+
111
+ Create empty node
112
+
113
+ ```ruby
114
+ >>> GOM.create!("/test")
115
+ '/test/c18bf546-e577-414a-92d2-2ebdfb69b4f6'
116
+ ```
117
+
118
+ Create node with attributes
119
+
120
+ ```ruby
121
+ >>> GOM.create!("/test", {"name":"Hans", "profession": "Lumberjack"})
122
+ '/test/419e9db0-2800-43ed-9053-edaafd4f60b3'
123
+ >>> GOM.retrieve("/test/419e9db0-2800-43ed-9053-edaafd4f60b3")
124
+ {'node': {'ctime': '2012-10-12T10:43:25+02:00',
125
+ 'entries': [{'attribute': {'ctime': '2012-10-12T10:43:25+02:00',
126
+ 'mtime': '2012-10-12T10:43:25+02:00',
127
+ 'name': 'name',
128
+ 'node': '/test/419e9db0-2800-43ed-9053-edaafd4f60b3',
129
+ 'type': 'string',
130
+ 'value': 'Hans'}},
131
+ {'attribute': {'ctime': '2012-10-12T10:43:25+02:00',
132
+ 'mtime': '2012-10-12T10:43:25+02:00',
133
+ 'name': 'profession',
134
+ 'node': '/test/419e9db0-2800-43ed-9053-edaafd4f60b3',
135
+ 'type': 'string',
136
+ 'value': 'Lumberjack'}}],
137
+ 'mtime': '2012-10-12T10:43:25+02:00',
138
+ 'uri': '/test/419e9db0-2800-43ed-9053-edaafd4f60b3'}}
139
+ ```
140
+
141
+ ## Packaging
142
+
143
+ ## TODO
144
+
145
+ * Document script runner (run_script)
146
+ * Document gom observer creation (register_observer)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,36 @@
1
+ require 'rake'
2
+
3
+ # -*- encoding: utf-8 -*-
4
+ $:.push File.expand_path("../lib", __FILE__)
5
+ require 'gom/client/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'gom-client'
9
+ s.version = Gom::Client::VERSION
10
+ s.date = Gom::Client::DATE
11
+ s.authors = "ART+COM"
12
+ s.homepage = 'http://www.artcom.de/'
13
+ s.summary = 'REST client for the gom HTTP API'
14
+
15
+ s.add_dependency 'json'
16
+
17
+ s.add_development_dependency 'rspec'
18
+ s.add_development_dependency 'guard'
19
+ s.add_development_dependency 'guard-rspec'
20
+ s.add_development_dependency 'rb-fsevent', '~>0.9.1'
21
+ s.add_development_dependency 'webmock'
22
+ s.add_development_dependency 'vcr'
23
+
24
+ if RUBY_PLATFORM.match /java/i
25
+ s.add_development_dependency 'ruby-debug'
26
+ else
27
+ s.add_development_dependency 'debugger'
28
+ end
29
+
30
+ s.files = `git ls-files`.split("\n")
31
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ s.executables = `git ls-files -- bin/*`.split("\n").map do |f|
33
+ File.basename(f)
34
+ end
35
+ s.require_paths = ["lib"]
36
+ end
@@ -0,0 +1,6 @@
1
+ module Gom
2
+ class Client
3
+ VERSION = '0.0.2'
4
+ DATE = '2013-08-15'
5
+ end
6
+ end
data/lib/gom/client.rb ADDED
@@ -0,0 +1,226 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ #require 'active_support'
4
+ require 'rexml/document'
5
+ require 'gom/client/version'
6
+ require 'json' #/pure'
7
+
8
+ module Gom
9
+
10
+ class HttpError < StandardError
11
+ attr_reader :response, :description
12
+
13
+ def initialize(http_response, description)
14
+ @response = http_response
15
+ @description = description
16
+ end
17
+
18
+ def to_s
19
+ "#{@response.code} #{@response.message} #{@description}"
20
+ end
21
+ end
22
+
23
+ class Client
24
+ attr_reader :root
25
+
26
+ def initialize(gom_root = 'http://gom')
27
+ @root = URI.parse(gom_root)
28
+ end
29
+
30
+ # returns nil but NOT raise error on non-existing attributes
31
+ def retrieve_val(path_or_uri)
32
+ (a = retrieve(path_or_uri)) && a[:attribute][:value]
33
+ end
34
+
35
+ def retrieve(path_or_uri, redirect_limit=10)
36
+ self.retrieve! path_or_uri, redirect_limit
37
+ rescue Gom::HttpError => e
38
+ return nil if e.response.code == "404"
39
+ raise e
40
+ end
41
+
42
+ def retrieve!(path_or_uri, redirect_limit=10)
43
+ uri = path_or_uri.kind_of?(URI) ? path_or_uri : URI.parse("#{@root}#{path_or_uri}")
44
+ response = Net::HTTP.new(uri.host, uri.port).request_get(uri.path, {"Accept" => "application/json" })
45
+
46
+ begin
47
+ if response.kind_of?(Net::HTTPSuccess)
48
+ return JSON.parse(response.body, symbolize_names: true)
49
+ end
50
+ rescue StandardError => e
51
+ raise HttpError.new(response, "#{e} -- could not parse body: '#{response.body}'")
52
+ end
53
+
54
+ if (redirect_limit == 0 && response.kind_of?(Net::HTTPRedirection))
55
+ raise "too many redirects"
56
+ end
57
+
58
+ if response.kind_of?(Net::HTTPRedirection)
59
+ return Gom::retrieve(response['location'], redirect_limit - 1)
60
+ end
61
+
62
+ raise HttpError.new(response, "while GETting #{uri.path}")
63
+ end
64
+
65
+ # GOM API Change: Destroy will NOT return error on non-existing attributes
66
+ # or nodes. So no error will every be raised from this methods
67
+ def destroy!(path_or_uri)
68
+ uri = path_or_uri.kind_of?(URI) ? path_or_uri : URI.parse("#{@root}#{path_or_uri}")
69
+ response = Net::HTTP.new(uri.host, uri.port).delete(uri.path)
70
+
71
+ return true if response.kind_of?(Net::HTTPSuccess)
72
+ raise HttpError.new(response, "while DELETEing #{uri.path}")
73
+ end
74
+
75
+ def destroy(uri)
76
+ self.destroy! uri
77
+ rescue Gom::HttpError => e
78
+ return nil if e.response.code == "404"
79
+ raise e
80
+ end
81
+
82
+ def create!(path_or_uri, attributes={})
83
+ uri = path_or_uri.kind_of?(URI) ? path_or_uri : URI.parse("#{@root}#{path_or_uri}")
84
+ request_body = attributes_to_xml attributes
85
+ headers = { 'Content-Type' => 'application/xml' }
86
+ response = Net::HTTP.new(uri.host, uri.port).request_post(uri.path, request_body, headers)
87
+
88
+ if response.kind_of?(Net::HTTPRedirection)
89
+ return URI.parse(response['location']).path
90
+ end
91
+
92
+ raise HttpError.new(response, "while CREATEing (#{uri.path})")
93
+ end
94
+
95
+ def update!(path_or_uri, hash_or_text=nil)
96
+ uri = path_or_uri.kind_of?(URI) ? path_or_uri : URI.parse("#{@root}#{path_or_uri}")
97
+
98
+ if is_attribute?(uri.path)
99
+ raise "update attribute call must include value" if hash_or_text.nil?
100
+ raise "update attribute value must be a string" unless hash_or_text.kind_of?(String)
101
+ response_format = "text/plain"
102
+ if hash_or_text != ""
103
+ doc = REXML::Document.new
104
+ attr_node = doc.add_element 'attribute'
105
+ attr_node.attributes['type'] = 'string';
106
+ attr_node.text = hash_or_text
107
+ request_body = doc.to_s
108
+ headers = { 'Content-Type' => 'application/xml',
109
+ 'Accept' => response_format }
110
+ else
111
+ request_body = "attribute=#{hash_or_text}&type=string"
112
+ headers = { 'Content-Type' => 'application/x-www-form-urlencoded',
113
+ 'Accept' => response_format }
114
+ end
115
+ else
116
+ raise "update node values must be a hash of attributes" unless hash_or_text.nil? or hash_or_text.kind_of?(Hash)
117
+ request_body = attributes_to_xml hash_or_text || {}
118
+ response_format = "application/json"
119
+ headers = { 'Content-Type' => 'application/xml',
120
+ 'Accept' => response_format }
121
+ end
122
+
123
+ #headers = { 'Content-Type' => 'application/xml',
124
+ # 'Accept' => response_format }
125
+
126
+ response = Net::HTTP.new(uri.host, uri.port).request_put(uri.path, request_body, headers)
127
+
128
+ return response.body if response.kind_of?(Net::HTTPSuccess) && response_format == "text/plain"
129
+ if response.kind_of?(Net::HTTPSuccess) && response_format == "application/json"
130
+ return JSON.parse(response.body, symbolize_names: true)
131
+ end
132
+ raise HttpError.new(response, "while PUTting #{uri.path}")
133
+ end
134
+ alias update update!
135
+
136
+ # supports stored and posted scripts
137
+ def run_script(options = {})
138
+ path = options[:path] || nil
139
+ script = options[:script] || nil
140
+ params = options[:params] || {}
141
+
142
+ if path.nil?
143
+ script.nil? and (raise ArgumentError, "must provide script OR path")
144
+ else
145
+ script and (raise ArgumentError, "must not provide script AND path")
146
+ params[:_script_path] = path
147
+ end
148
+
149
+ params = params.keys.zip(params.values).map {|k,v| "#{k}=#{v}"}
150
+ url = URI.parse("#{@root}/gom/script/v8?#{params.join('&')}")
151
+
152
+ response = Net::HTTP.start(url.host, url.port) do |http|
153
+ if script
154
+ request = Net::HTTP::Post.new(url.to_s)
155
+ request.set_content_type "text/javascript"
156
+ http.request(request, script)
157
+ else
158
+ request = Net::HTTP::Get.new(url.to_s)
159
+ http.request(request)
160
+ end
161
+ end
162
+
163
+ if response.kind_of?(Net::HTTPSuccess)
164
+ return response
165
+ else
166
+ raise HttpError.new(
167
+ response, "while executing server-side-script:\n#{response.body}"
168
+ )
169
+ end
170
+ end
171
+
172
+ # supports anonymous and named observers
173
+ def register_observer(options={})
174
+ name = options[:name] || nil
175
+ callback_url = options[:callback_url] || nil
176
+ node = options[:node] || nil
177
+ filters = options[:filters] || {}
178
+ format = options[:format] || "application/json"
179
+
180
+ callback_url.nil? and (raise ArgumentError, "callback_url must not be nil")
181
+ node.nil? and (raise ArgumentError, "node must not be nil")
182
+ unless ['application/json', 'application/xml'].include?(format)
183
+ raise ArgumentError, "invalid format: '#{format}'"
184
+ end
185
+
186
+ url = URI.parse("#{@root}/gom/observer#{node}")
187
+ form_data = { 'callback_url' => callback_url,
188
+ 'accept' => format }
189
+ form_data.merge!(filters)
190
+
191
+ if name
192
+ observer_url = url.path.gsub(/\:/,'/')
193
+ my_uri = "#{observer_url}/.#{name}"
194
+ destroy my_uri
195
+
196
+ form_data['observed_uri'] = "#{node}"
197
+ update(my_uri, form_data)
198
+ else
199
+ request = Net::HTTP::Post.new(url.path)
200
+ request.set_form_data(form_data)
201
+ response = Net::HTTP.new(url.host, url.port).start do |http|
202
+ http.request(request)
203
+ end
204
+ my_uri = URI.parse(response['location']).path
205
+ end
206
+ my_uri
207
+ end
208
+
209
+ private
210
+
211
+ def is_attribute?(the_path)
212
+ not the_path.index(":").nil?
213
+ end
214
+
215
+ def attributes_to_xml(hash={})
216
+ doc = REXML::Document.new
217
+ node = doc.add_element 'node'
218
+ hash.each do |key, value|
219
+ attrib = node.add_element 'attribute'
220
+ attrib.attributes['name'] = key
221
+ attrib.text = value
222
+ end
223
+ doc.to_s
224
+ end
225
+ end
226
+ end