369db35be723a20ac799176cecf1a0028efabd35
[arvados.git] / lib / google / api_client.rb
1 # Copyright 2010 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 require 'httpadapter'
16 require 'json'
17
18 require 'google/api_client/discovery'
19
20 module Google #:nodoc:
21   ##
22   # This class manages communication with a single API.
23   class APIClient
24
25     def initialize(options={})
26       @options = {
27         # TODO: What configuration options need to go here?
28       }.merge(options)
29     end
30
31     ##
32     # Returns the parser used by the client.
33     def parser
34       unless @options[:parser]
35         require 'google/api_client/parsers/json_parser'
36         # NOTE: Do not rely on this default value, as it may change
37         @options[:parser] = JSONParser
38       end
39       return @options[:parser]
40     end
41
42     ##
43     # Returns the authorization mechanism used by the client.
44     def authorization
45       unless @options[:authorization]
46         require 'signet/oauth_1/client'
47         # NOTE: Do not rely on this default value, as it may change
48         @options[:authorization] = Signet::OAuth1::Client.new(
49           :temporary_credential_uri =>
50             'https://www.google.com/accounts/OAuthGetRequestToken',
51           :authorization_uri =>
52             'https://www.google.com/accounts/OAuthAuthorizeToken',
53           :token_credential_uri =>
54             'https://www.google.com/accounts/OAuthGetAccessToken',
55           :client_credential_key => 'anonymous',
56           :client_credential_secret => 'anonymous'
57         )
58       end
59       return @options[:authorization]
60     end
61
62     ##
63     # Returns the HTTP adapter used by the client.
64     def http_adapter
65       return @options[:http_adapter] ||= (begin
66         require 'httpadapter/adapters/net_http'
67         @options[:http_adapter] = HTTPAdapter::NetHTTPRequestAdapter
68       end)
69     end
70
71     def discovery_uri
72       return @options[:discovery_uri] ||= (begin
73         if @options[:service]
74           service_id = @options[:service]
75           service_version = @options[:service_version] || 'v1'
76           "http://www.googleapis.com/discovery/0.1/describe" +
77           "?api=#{service_id}"
78         else
79           raise ArgumentError,
80             'Missing required configuration value, :discovery_uri.'
81         end
82       end)
83     end
84
85     def discovery_document
86       return @discovery_document ||= (begin
87         request = ['GET', self.discovery_uri, [], []]
88         response = self.transmit_request(request)
89         status, headers, body = response
90         if status == 200
91           merged_body = StringIO.new
92           body.each do |chunk|
93             merged_body.write(chunk)
94           end
95           merged_body.rewind
96           JSON.parse(merged_body.string)
97         else
98           raise TransmissionError,
99             "Could not retrieve discovery document at: #{self.discovery_uri}"
100         end
101       end)
102     end
103
104     def discovered_services
105       return @discovered_services ||= (begin
106         service_names = self.discovery_document['data'].keys()
107         services = []
108         for service_name in service_names
109           versions = self.discovery_document['data'][service_name]
110           for service_version in versions.keys()
111             service_description =
112               self.discovery_document['data'][service_name][service_version]
113             services << ::Google::APIClient::Service.new(
114               service_name,
115               service_version,
116               service_description
117             )
118           end
119         end
120         services
121       end)
122     end
123
124     def discovered_service(service_name, service_version='v1')
125       for service in self.discovered_services
126         if service.name == service_name &&
127             service.version.to_s == service_version.to_s
128           return service
129         end
130       end
131       return nil
132     end
133
134     def discovered_method(rpc_name, service_version='v1')
135       for service in self.discovered_services
136         # This looks kinda weird, but is not a real problem because there's
137         # almost always only one service, and this is memoized anyhow.
138         if service.version.to_s == service_version.to_s
139           return service.to_h[rpc_name] if service.to_h[rpc_name]
140         end
141       end
142       return nil
143     end
144
145     def latest_service(service_name)
146       versions = {}
147       for service in self.discovered_services
148         next if service.name != service_name
149         sortable_version = service.version.gsub(/^v/, '').split('.').map do |v|
150           v.to_i
151         end
152         versions[sortable_version] = service
153       end
154       return versions[versions.keys.sort.last]
155     end
156
157     def generate_request(
158         api_method, parameters={}, body='', headers=[], options={})
159       options={
160         :signed => true,
161         :parser => self.parser,
162         :service_version => 'v1'
163       }.merge(options)
164       if api_method.kind_of?(String)
165         api_method = self.discovered_method(
166           api_method, options[:service_version]
167         )
168       elsif !api_method.kind_of?(::Google::APIClient::Service)
169         raise TypeError,
170           "Expected String or Google::APIClient::Service, " +
171           "got #{api_method.class}."
172       end
173       request = api_method.generate_request(parameters, body, headers)
174       if options[:signed]
175         request = self.sign_request(request)
176       end
177       return request
178     end
179
180     def execute(api_method, parameters={}, body='', headers=[], options={})
181       request = self.generate_request(
182         api_method, parameters, body, headers, options
183       )
184       return self.transmit_request(request)
185     end
186
187     def transmit_request(request, adapter=self.http_adapter)
188       ::HTTPAdapter.transmit(request, adapter)
189     end
190
191     def sign_request(request)
192       if self.authorization.respond_to?(:generate_authenticated_request)
193         return self.authorization.generate_authenticated_request(
194           :request => request
195         )
196       else
197         raise TypeError,
198           'Expected authorization mechanism to respond to ' +
199           '#generate_authenticated_request.'
200       end
201     end
202   end
203 end
204
205 require 'google/api_client/version'