bluevia 1.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.
@@ -0,0 +1,401 @@
1
+ #
2
+ # BlueVia is a global iniciative of Telefonica delivered by Movistar and O2.
3
+ # Please, check out www.bluevia.com and if you need more information
4
+ # contact us at mailto:support@bluevia.com
5
+
6
+ require 'net/http'
7
+ require 'net/https'
8
+ require 'uri'
9
+ require 'cgi'
10
+ require 'bluevia/utils'
11
+ require 'bluevia/response'
12
+ require 'bluevia/ext/hash'
13
+ require 'bluevia/utils'
14
+ require 'bluevia/errors'
15
+ require 'bluevia/bluevia_logger'
16
+ require 'oauth/client/helper'
17
+
18
+ module Bluevia
19
+ #
20
+ # Abstract class that wraps access to REST services
21
+ #
22
+
23
+ class BaseClient
24
+ include BlueviaLogger
25
+
26
+ attr_accessor :base_uri
27
+
28
+ #
29
+ # REST INTERFACE CONFIGURATION
30
+ #
31
+
32
+ # Base URI to invoke Bluevia API. Production environment
33
+ BASEURI = "https://api.bluevia.com"
34
+
35
+ # Base Path to invoke Bluevia API. Production environment
36
+ BASEPATH = "/services/REST"
37
+
38
+ # Default parameters required to invoke API
39
+ DEFAULT_PARAMS = {:version => "v1", :alt => "json"}
40
+ # Endpoint for commercial (either testing or commercial apps)
41
+ BASEPATH_COMMERCIAL = ""
42
+ # Endpoint for sandbox (either testing or commercial apps)
43
+ BASEPATH_SANDBOX = "_Sandbox"
44
+
45
+ # HTTP Proxy is SDK is being used behind a firewall
46
+ PROXY = nil #"localhost:8888" #, "nube.hi.inet:8080"
47
+
48
+ def BaseClient.create_rest_client(uri = nil)
49
+ @@base_uri = uri.nil? ? BASEURI : uri
50
+ @@uri = URI.parse(@@base_uri)
51
+
52
+ # Set proxy if required
53
+ unless PROXY.nil?
54
+ proxy = PROXY.split(":")
55
+ rest = Net::HTTP::Proxy(proxy[0], proxy[1]).new(@@uri.host, @@uri.port)
56
+ else
57
+ rest = Net::HTTP.new(@@uri.host, @@uri.port)
58
+ end
59
+ # Set HTTP connection with SSL if required
60
+ if @@uri.instance_of?(URI::HTTPS)
61
+ rest.use_ssl = true
62
+ end
63
+ rest.read_timeout=(5)
64
+ rest
65
+ end
66
+
67
+
68
+ #
69
+ # Creates basic HTTP client
70
+ #
71
+ def initialize(params = nil)
72
+ @@uri = URI.parse(BASEURI)
73
+
74
+ if params.nil?
75
+ @rest = BaseClient.create_rest_client
76
+ elsif params.instance_of?(Hash)
77
+ params.has_key?(:rest) and @rest = params[:rest]
78
+ params.has_key?(:logger) and logger = params[:logger]
79
+ else
80
+ @rest = params
81
+ end
82
+
83
+ logger.debug "Initialized baseClient for service: #{self.class.name}"
84
+ end
85
+
86
+ #
87
+ # set HTTP client
88
+ #
89
+ def set_http_client(rest)
90
+ @rest = rest
91
+ end
92
+
93
+ #
94
+ # set the valid timeout for the HTTP requests
95
+ #
96
+ def set_timeout(timeout = 5)
97
+ !timeout.nil? and @rest.read_timeout(timeout)
98
+ end
99
+
100
+ #
101
+ # Define an instance variable using the default Hash syntax
102
+ #
103
+ def []=(key, value)
104
+ self.instance_variable_set(:"@#{key}", value) unless key.nil?
105
+ end
106
+
107
+ #
108
+ # Creates a valid path by concat the path received
109
+ # with the path defined in BASEPATH
110
+ #
111
+ def set_path(_path = nil)
112
+ path = BASEPATH.clone
113
+ path << _path unless _path.nil?
114
+ end
115
+
116
+ #
117
+ # Include params in the URI being created to perform a REST call
118
+ #
119
+ def include_params(_path, _params)
120
+ "#{_path}?".
121
+ concat(
122
+ _params.collect{
123
+ |k,v|
124
+ "#{k}=#{CGI::escape(v.to_s)}"
125
+ }.reverse.join('&')) unless _params.nil?
126
+ end
127
+
128
+ # Each Bluevia API has a specific basepath. This method gets the specific
129
+ # service basepath and the path associated to the request (either test or
130
+ # commercial)
131
+ def get_basepath
132
+ begin
133
+ basepath = self.class.const_get("BASEPATH_API")
134
+ rescue NameError => e
135
+ logger.error "Unable to fetch basepath #{e}"
136
+ basepath = "/#{self.class.to_s.upcase}_"
137
+ end
138
+
139
+ if @commercial
140
+ path = BASEPATH_COMMERCIAL
141
+ else
142
+ path = BASEPATH_SANDBOX
143
+ end
144
+ "#{basepath}#{path}"
145
+ end
146
+
147
+ #
148
+ # Send an Http::Get to the server
149
+ #
150
+ def GET(_path = nil, params = nil, headers = nil, field = nil)
151
+ path = set_path(_path)
152
+ params = Hash.new if params.nil?
153
+ params.merge!(DEFAULT_PARAMS) { |key,oldval,newval| oldval }
154
+
155
+ path = include_params(path, params)
156
+
157
+ logger.debug "GET Request path: " << path
158
+
159
+ begin
160
+ resp = authorized_client.get(path, get_headers(headers))
161
+ rescue => e
162
+ logger.error e
163
+ end
164
+ return handle_response(create_response(resp), "body", field)
165
+ end
166
+
167
+ #
168
+ # Send an Http::Post to the server
169
+ #
170
+ def POST(_path = nil, body = nil, headers = nil, files = nil, return_header = nil)
171
+
172
+ if files.nil?
173
+ path = set_path(_path)
174
+ path = include_params(path, DEFAULT_PARAMS)
175
+ logger.debug "POST Request path: " << path
176
+ logger.debug "POST body: " << body
177
+
178
+ begin
179
+ resp = authorized_client.post(path, body, get_headers(headers, true))
180
+ rescue => e
181
+ logger.error e
182
+ end
183
+ return handle_response(create_response(resp), return_header.nil? ? nil : "headers", return_header)
184
+ else
185
+ return POST_MULTIPART(_path, body, headers, files)
186
+ end
187
+ end
188
+
189
+ #
190
+ # Send an Http::Delete to the server
191
+ #
192
+ def DELETE(_path = nil, headers = nil)
193
+ path = set_path(_path)
194
+ path = include_params(path, DEFAULT_PARAMS)
195
+
196
+ logger.debug "DELETE Request path: " << path
197
+
198
+ resp = authorized_client.delete(path, get_headers(headers, false))
199
+ return handle_response(create_response(resp))
200
+ end
201
+
202
+ def authorized_client
203
+ @consumer ||= OAuth::Consumer.new(@consumer_key, @consumer_secret, :site => @@base_uri)
204
+ @access_token ||= OAuth::AccessToken.new(@consumer, @token, @token_secret)
205
+ @access_token
206
+ end
207
+
208
+ #
209
+ # Creates the basic header while creating an HTTP Request
210
+ #
211
+ def get_headers(_headers = nil, is_post = false)
212
+
213
+ headers = {
214
+ "Accept" => "application/json"
215
+ }
216
+
217
+ if is_post
218
+ headers["Content-Type"] = "application/json"
219
+ end
220
+
221
+ unless _headers.nil?
222
+ headers.merge!(_headers) { |key,oldval,newval| newval }
223
+ end
224
+ logger.debug "HEADERS: "
225
+ logger.debug headers
226
+ return headers
227
+ end
228
+
229
+ private
230
+
231
+ # Converts an HTTP response to an internal object
232
+ def create_response(http_response)
233
+ return nil if http_response.nil?
234
+
235
+ response = Response.new
236
+ response.code = http_response.code
237
+ response.message = http_response.message
238
+
239
+ # headers
240
+ response.headers = Hash.new
241
+
242
+ http_response.each_header {|key, value|
243
+ response.headers[key] = value
244
+ }
245
+
246
+ # body
247
+ begin
248
+ # Convert JSON
249
+ if response.headers["content-type"].to_s.start_with?("application/json")
250
+ unless http_response.body.nil? or http_response.body.empty?
251
+ response.body = JSON.parse(http_response.body)
252
+ else
253
+ response.body = ""
254
+ end
255
+ # Convert XML to Hash
256
+ elsif response.headers["content-type"].to_s.start_with?("application/xml")
257
+ begin
258
+ response.body = Hash.from_xml(http_response.body)
259
+ rescue => e
260
+ logger.error "Unable to decode response: #{e.to_s}"
261
+ raise ServerError, "Unable to decode XML response"
262
+ end
263
+ # Do nothing
264
+ else
265
+ response.body = http_response.body
266
+ end
267
+ rescue => e
268
+ logger.error "Error while converting response"
269
+ logger.error e
270
+ raise ServerError, "Error while processing the response"
271
+ end
272
+ logger.debug response.to_s
273
+ return response
274
+ end
275
+
276
+ #
277
+ # This method is in charge of verify the code retrieved from the server and
278
+ # handle it properly.
279
+ # In case of get a field, just this JSON filed is retrieved from the body response
280
+ #
281
+ # Examples:
282
+ #
283
+ # response.code = 200
284
+ # response.body = {"name": "foo", "surname": "bar", "age": 32, "address": "Nissim Aloni"}
285
+ #
286
+ # handle_response(response, {"name"}) => "foo"
287
+ # handle_response(response) => {"name": "foo", "surname": "bar", "age": 32, "address": "Nissim Aloni"}
288
+ # handle_response(response, {"age"}) => 32
289
+ #
290
+ def handle_response(response, field_name = nil, field_value = nil)
291
+ unless response.nil?
292
+
293
+ if !field_name.nil? && field_value.instance_of?(String)
294
+ field_value = [field_value]
295
+ end
296
+
297
+ # Valid response
298
+ if response.code =~ /20?/
299
+ unless field_name.nil?
300
+ response_field = response[field_name]
301
+ if field_value.nil?
302
+ return response_field
303
+ end
304
+ unless response_field.nil?
305
+ if response_field.instance_of?(Hash)
306
+ unless field_value.nil? || !field_value.instance_of?(Array)
307
+ field_value.each{ |key|
308
+ unless response_field.has_key?(key)
309
+ raise ClientError, "Unable to find the request data: #{key}"
310
+ end
311
+ response_field = response_field[key]
312
+ }
313
+ end
314
+ return response_field
315
+ end
316
+ else
317
+ return false
318
+ end
319
+ else
320
+ return response
321
+ end
322
+ # Not found
323
+ elsif response.code.eql?("404")
324
+ raise NotFoundError, "Unable to find the resource: #{response.message} - #{response.body}"
325
+ # 40?
326
+ elsif response.code =~ /40?/
327
+ raise ClientError, "Error #{response.code} received: #{response.message} - #{response.body}"
328
+ # Not implemented
329
+ elsif response.code =~ /501/
330
+ raise NotImplementedError, "Operation not implemented in server side #{response.message} - #{response.body}"
331
+ # Server Error
332
+ elsif response.code =~ /500/
333
+ raise ServerError, "Server error #{response.message} - #{response.body}"
334
+ end
335
+ else
336
+ raise ServerError, "Unable to connect with endpoint."
337
+ end
338
+ end
339
+
340
+ #
341
+ # Send an Http::Post::Multipart to the server
342
+ #
343
+ def POST_MULTIPART(_path = nil, body = nil, headers = nil, files = nil)
344
+ path = set_path(_path)
345
+ path = include_params(path, DEFAULT_PARAMS)
346
+ unless files.nil?
347
+ logger.debug "POST MULTIPART " << path
348
+ multipart_data = Hash.new
349
+ #prueba = Utils::Multipart.new
350
+ unless files.nil?
351
+ if files.instance_of?(String) or (files.instance_of?(Array) and files.length == 1)
352
+ if files.instance_of?(Array)
353
+ files = files[0]
354
+ end
355
+ multipart_data["attachments"] = UploadIO.new(files, Utils.get_file_mime_type(files))
356
+ elsif files.instance_of?(Array)
357
+ requestbody = String.new
358
+ mimeboundary = "ABC123Boundary"
359
+ files.each{|file|
360
+ requestbody << "\r\n"
361
+ requestbody << "--#{mimeboundary}\n"
362
+ requestbody << "\r\n"
363
+ requestbody << "Content-Disposition: attachment; fileName=\"text.txt\""
364
+ requestbody << "\r\n"
365
+ requestbody << "Content-Type: #{Utils.get_file_mime_type(file)}\n"
366
+ requestbody << "\r\n"
367
+ content = File.open(file, "rb")
368
+ requestbody << "#{content.read}\n"
369
+ }
370
+ requestbody << "--#{mimeboundary}--\n"
371
+ multipart_data["attachment"] = UploadIO.new(StringIO.new(requestbody), "multipart/mixed; boundary=#{mimeboundary}", "attachments")
372
+ elsif files.instance_of?(Hash) && files.has_key?(:text)
373
+ multipart_data["attachments"] = UploadIO.new(StringIO.new(files[:text]), "text/plain", "text")
374
+ else
375
+ logger.error "Not sure about how to send this object: #{files.class.name}"
376
+ end
377
+ end
378
+ multipart_data["message"] = UploadIO.new(StringIO.new(body), "application/json", "root-field")
379
+ logger.debug "MULTIPART POST message: #{body}"
380
+ req = Net::HTTP::Post::Multipart.new(
381
+ path,
382
+ multipart_data,
383
+ get_headers(headers, true)
384
+ )
385
+
386
+ # Set proxy if required
387
+ unless PROXY.nil?
388
+ proxy = PROXY.split(":")
389
+ rest = Net::HTTP::Proxy(proxy[0], proxy[1]).new(@@uri.host,@@uri.port)
390
+ else
391
+ rest = Net::HTTP.new(@@uri.host,@@uri.port)
392
+ end
393
+ res = rest.start do |http|
394
+ http.request(req)
395
+ end
396
+
397
+ return create_response(res)
398
+ end
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,119 @@
1
+ #
2
+ # BlueVia is a global iniciative of Telefonica delivered by Movistar and O2.
3
+ # Please, check out www.bluevia.com and if you need more information
4
+ # contact us at mailto:support@bluevia.com
5
+
6
+ require 'rubygems'
7
+ require 'json'
8
+ require 'uri'
9
+ require 'net/http'
10
+ require 'bluevia/directory'
11
+ require 'bluevia/sms'
12
+ require 'bluevia/oauth'
13
+ require 'bluevia/advertising'
14
+ require 'bluevia/bluevia_logger'
15
+
16
+ module Bluevia
17
+ #
18
+ # This class is the main client to access Bluevia API.
19
+ # It defines a services factory that provides an isolated way to reach
20
+ # each API. Currently the following APIs are supported:
21
+ # * oAuth authentication
22
+ # * SMS
23
+ # * Advertising
24
+ # * Directory
25
+ #
26
+ # When creating a BlueviaClient instance, basic authentication parameters
27
+ # can be provided:
28
+ #
29
+ # require 'bluevia'
30
+ #
31
+ # bc = BlueviaClient.new(
32
+ # { :consumer_key => <YOUR_CONSUMER_KEY>,
33
+ # :consumer_secret => <YOUR_CONSUMER_SECRET>,
34
+ # :token => <OAUTH_TOKEN>,
35
+ # :token_secret => <OAUTH_TOKEN_SECRET>
36
+ # })
37
+ #
38
+
39
+ class BlueviaClient
40
+ include BlueviaLogger
41
+
42
+ # Constructor
43
+ def initialize(params = nil)
44
+
45
+ unless params.nil? || params[:uri].nil?
46
+ rest = BaseClient.create_rest_client(params[:uri])
47
+ else
48
+ rest = BaseClient.create_rest_client
49
+ end
50
+
51
+ @commercial = false
52
+ @consumer_key = nil
53
+ @consumer_secret = nil
54
+
55
+ unless params.nil?
56
+ %w(consumer_key consumer_secret token token_secret).each{ |param|
57
+ self.instance_variable_set(:"@#{param}", params[:"#{param}"]) unless params[:"#{param}"].nil?
58
+ }
59
+ end
60
+
61
+ @factory = ServicesFactory.new(rest)
62
+ end
63
+
64
+ # Retrieves a specific service to call any related API
65
+ # i.e.
66
+ # sms = bc.get_service(:Sms)
67
+ def get_service(_service)
68
+ service = @factory.get(_service)
69
+ service[:consumer_key] = @consumer_key unless @consumer_key.nil?
70
+ service[:consumer_secret] = @consumer_secret unless @consumer_secret.nil?
71
+ service[:token] = @token unless @token.nil?
72
+ service[:token_secret] = @token_secret unless @token_secret.nil?
73
+ service[:commercial] = @commercial
74
+ return service
75
+ end
76
+
77
+ # Client will get access to commercial APIs
78
+ def set_commercial
79
+ @commercial = true
80
+ end
81
+
82
+ # Client will get access to sandbox APIs
83
+ def set_sandbox
84
+ @commercial = false
85
+ end
86
+
87
+ # {true|false} if commercial or sandbox client
88
+ def commercial?
89
+ return @commercial
90
+ end
91
+ end
92
+
93
+ # This service factory wraps the initialization of a service
94
+ class ServicesFactory
95
+ include BlueviaLogger
96
+ def initialize(rest = nil)
97
+ @directory = Directory.new({:rest =>rest, :logger => logger})
98
+ @sms = Sms.new({:rest =>rest, :logger => logger})
99
+ @advertising = Advertising.new({:rest =>rest, :logger => logger})
100
+ @oauth = Oauth.new({:rest =>rest, :logger => logger})
101
+ end
102
+
103
+ def get(service)
104
+ case service.to_s.downcase
105
+ when "directory"
106
+ return @directory
107
+ when "sms"
108
+ return @sms
109
+ when "oauth"
110
+ return @oauth
111
+ when "advertising"
112
+ return @advertising
113
+ else
114
+ raise(SyntaxError, "Service not valid")
115
+ end
116
+ end
117
+
118
+ end
119
+ end