opensocial 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2008 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
+
16
+ module OpenSocial #:nodoc:
17
+
18
+ # Acts as a wrapper for an OpenSocial group.
19
+ #
20
+ # The Group class takes input JSON as an initialization parameter, and
21
+ # iterates through each of the key/value pairs of that JSON. For each key
22
+ # that is found, an attr_accessor is constructed, allowing direct access
23
+ # to the value. Each value is stored in the attr_accessor, either as a
24
+ # String, Fixnum, Hash, or Array.
25
+ #
26
+
27
+
28
+ class Group < Base
29
+
30
+ # Initializes the Group based on the provided json fragment. If no JSON
31
+ # is provided, an empty object (with no attributes) is created.
32
+ def initialize(json)
33
+ if json
34
+ json.each do |key, value|
35
+ proper_key = key.snake_case
36
+ begin
37
+ self.send("#{proper_key}=", value)
38
+ rescue NoMethodError
39
+ add_attr(proper_key)
40
+ self.send("#{proper_key}=", value)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Provides the ability to request a Collection of groups for a given
48
+ # user.
49
+ #
50
+ # The FetchGroupsRequest wraps a simple request to an OpenSocial
51
+ # endpoint for a collection of groups. As parameters, it accepts
52
+ # a user ID. This request may be used, standalone, by calling send, or
53
+ # bundled into an RpcRequest.
54
+ #
55
+
56
+
57
+ class FetchGroupsRequest < Request
58
+ # Defines the service fragment for use in constructing the request URL or
59
+ # JSON
60
+ SERVICE = 'groups'
61
+
62
+ # Initializes a request to fetch groups for the specified user, or the
63
+ # default (@me). A valid Connection is not necessary if the request is to
64
+ # be used as part of an RpcRequest.
65
+ def initialize(connection = nil, guid = '@me')
66
+ super
67
+ end
68
+
69
+ # Sends the request, passing in the appropriate SERVICE and specified
70
+ # instance variables.
71
+ def send
72
+ json = send_request(SERVICE, @guid)
73
+
74
+ groups = Collection.new
75
+ for entry in json['entry']
76
+ group = Group.new(entry)
77
+ groups[group.id] = group
78
+ end
79
+
80
+ return groups
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,198 @@
1
+ # Copyright (c) 2008 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
+ # Extends the OpenSocial module, providing a wrapper for OpenSocial people.
16
+ #
17
+ # Person: Acts as a wrapper for an OpenSocial person.
18
+ # FetchPersonRequest: Provides the ability to request a single person.
19
+ # FetchPeopleRequest: Provides the ability to request a collection of people
20
+ # by describing their relationship to a single person.
21
+ #
22
+
23
+
24
+ module OpenSocial #:nodoc:
25
+
26
+ # Acts as a wrapper for an OpenSocial person.
27
+ #
28
+ # The Person class takes input JSON as an initialization parameter, and
29
+ # iterates through each of the key/value pairs of that JSON. For each key
30
+ # that is found, an attr_accessor is constructed, allowing direct access
31
+ # to the value. Each value is stored in the attr_accessor, either as a
32
+ # String, Fixnum, Hash, or Array.
33
+ #
34
+
35
+
36
+ class Person < Base
37
+
38
+ # Initializes the Person based on the provided json fragment. If no JSON
39
+ # is provided, an empty object (with no attributes) is created.
40
+ def initialize(json)
41
+ if json
42
+ json.each do |key, value|
43
+ proper_key = key.snake_case
44
+ begin
45
+ self.send("#{proper_key}=", value)
46
+ rescue NoMethodError
47
+ add_attr(proper_key)
48
+ self.send("#{proper_key}=", value)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Returns the complete name of the Person, to the best ability, given
55
+ # available fields.
56
+ def long_name
57
+ if @name && @name['givenName'] && @name['familyName']
58
+ return @name['givenName'] + ' ' + @name['familyName']
59
+ elsif @nickname
60
+ return @nickname
61
+ else
62
+ return ''
63
+ end
64
+ end
65
+
66
+ # Returns the first name or nickname of the Person, to the best ability,
67
+ # given available fields.
68
+ def short_name
69
+ if @name && @name['givenName']
70
+ return @name['givenName']
71
+ elsif @nickname
72
+ return @nickname
73
+ else
74
+ return ''
75
+ end
76
+ end
77
+ end
78
+
79
+ # Provides the ability to request a single Person.
80
+ #
81
+ # The FetchPeopleRequests wraps a simple request to an OpenSocial
82
+ # endpoint for a single person. As parameters, it accepts a user ID and
83
+ # selector and optionally an ID of a particular person, in order to display
84
+ # the user ID's view of that person. This request may be used, standalone,
85
+ # by calling send, or bundled into an RpcRequest.
86
+ #
87
+
88
+
89
+ class FetchPersonRequest < Request
90
+ # Defines the service fragment for use in constructing the request URL or
91
+ # JSON
92
+ SERVICE = 'people'
93
+
94
+ # Initializes a request to the specified user, or the default (@me, @self).
95
+ # A valid Connection is not necessary if the request is to be used as part
96
+ # of an RpcRequest.
97
+ def initialize(connection = nil, guid = '@me', selector = '@self')
98
+ super
99
+ end
100
+
101
+ # Sends the request, passing in the appropriate SERVICE and specified
102
+ # instance variables.
103
+ def send
104
+ json = send_request(SERVICE, @guid, @selector)
105
+
106
+ return parse_response(json['entry'])
107
+ end
108
+
109
+ # Selects the appropriate fragment from the JSON response in order to
110
+ # create a native object.
111
+ def parse_rpc_response(response)
112
+ return parse_response(response['data'])
113
+ end
114
+
115
+ # Converts the request into a JSON fragment that can be used as part of a
116
+ # larger RpcRequest.
117
+ def to_json(*a)
118
+ value = {
119
+ 'method' => SERVICE + GET,
120
+ 'params' => {
121
+ 'userId' => ["#{@guid}"],
122
+ 'groupId' => "#{@selector}"
123
+ },
124
+ 'id' => @key
125
+ }.to_json(*a)
126
+ end
127
+
128
+ private
129
+
130
+ # Converts the JSON response into a person.
131
+ def parse_response(response)
132
+ return Person.new(response)
133
+ end
134
+ end
135
+
136
+ # Provides the ability to request a Collection of people by describing their
137
+ # relationship to a single person.
138
+ #
139
+ # The FetchPeopleRequests wraps a simple request to an OpenSocial
140
+ # endpoint for a Collection of people. As parameters, it accepts
141
+ # a user ID and selector. This request may be used, standalone, by calling
142
+ # send, or bundled into an RpcRequest.
143
+ #
144
+
145
+
146
+ class FetchPeopleRequest < Request
147
+ # Defines the service fragment for use in constructing the request URL or
148
+ # JSON
149
+ SERVICE = 'people'
150
+
151
+ # Initializes a request to the specified user's group, or the default (@me,
152
+ # @friends). A valid Connection is not necessary if the request is to be
153
+ # used as part of an RpcRequest.
154
+ def initialize(connection = nil, guid = '@me', selector = '@friends')
155
+ super
156
+ end
157
+
158
+ # Sends the request, passing in the appropriate SERVICE and specified
159
+ # instance variables.
160
+ def send
161
+ json = send_request(SERVICE, @guid, @selector)
162
+
163
+ return parse_response(json['entry'])
164
+ end
165
+
166
+ # Selects the appropriate fragment from the JSON response in order to
167
+ # create a native object.
168
+ def parse_rpc_response(response)
169
+ return parse_response(response['data']['list'])
170
+ end
171
+
172
+ # Converts the request into a JSON fragment that can be used as part of a
173
+ # larger RPC request.
174
+ def to_json(*a)
175
+ value = {
176
+ 'method' => SERVICE + GET,
177
+ 'params' => {
178
+ 'userId' => ["#{@guid}"],
179
+ 'groupId' => "#{@selector}"
180
+ },
181
+ 'id' => @key
182
+ }.to_json(*a)
183
+ end
184
+
185
+ private
186
+
187
+ # Converts the JSON response into a Collection of people, indexed by id.
188
+ def parse_response(response)
189
+ people = Collection.new
190
+ for entry in response
191
+ person = Person.new(entry)
192
+ people[person.id] = person
193
+ end
194
+
195
+ return people
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,273 @@
1
+ # Copyright (c) 2008 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
+
16
+ module OpenSocial #:nodoc:
17
+
18
+ # Provides a wrapper for a single request to an OpenSocial endpoint, either
19
+ # as a standalone request, or as a fragment of an RPC request.
20
+ #
21
+ # The Request class wraps an HTTP request to an OpenSocial endpoint for
22
+ # social data. A Request may be used, directly, to access social resources
23
+ # that may not be currently wrapped by Request's child classes. Used in this
24
+ # was it gives near-raw access to social data. The Request class supplies
25
+ # OAuth signing when the supplied connection contains appropriate
26
+ # credentials.
27
+ #
28
+
29
+
30
+ class Request
31
+ GET = '.get'
32
+
33
+ # Defines the connection that will be used in the request.
34
+ attr_accessor :connection
35
+
36
+ # Defines the guid for the request.
37
+ attr_accessor :guid
38
+
39
+ # Defines the selector for the request.
40
+ attr_accessor :selector
41
+
42
+ # Defines the pid for the request.
43
+ attr_accessor :pid
44
+
45
+ # Defines the key used to lookup the request result in an RPC request.
46
+ attr_accessor :key
47
+
48
+ # Initializes a request using the optionally supplied connection, guid,
49
+ # selector, and pid.
50
+ def initialize(connection = nil, guid = nil, selector = nil, pid = nil)
51
+ @connection = connection
52
+ @guid = guid
53
+ @selector = selector
54
+ @pid = pid
55
+ end
56
+
57
+ # Generates a request given the service, guid, selector, and pid, to the
58
+ # OpenSocial endpoint by constructing the service URI and dispatching the
59
+ # request. When data is returned, it is parsed as JSON after being
60
+ # optionally unescaped.
61
+ def send_request(service, guid, selector = nil, pid = nil,
62
+ unescape = false)
63
+ if !@connection
64
+ raise RequestException.new('Request requires a valid connection.')
65
+ end
66
+
67
+ uri = @connection.service_uri(@connection.container[:rest] + service,
68
+ guid, selector, pid)
69
+ data = dispatch(uri)
70
+
71
+ if unescape
72
+ JSON.parse(data.os_unescape)
73
+ else
74
+ JSON.parse(data)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # Dispatches a request to a given URI with optional POST data. If a
81
+ # request's connection has specified HMAC-SHA1 authentication, OAuth
82
+ # parameters and signature are appended to the request.
83
+ def dispatch(uri, post_data = nil)
84
+ http = Net::HTTP.new(uri.host, uri.port)
85
+
86
+ if post_data
87
+ if @connection.container[:use_request_body_hash]
88
+ hash = request_body_hash(post_data)
89
+
90
+ # Appends the hash parameter
91
+ query = uri.query
92
+ if query
93
+ uri.query = query + '&oauth_body_hash=' + hash
94
+ else
95
+ uri.query = 'oauth_body_hash=' + hash
96
+ end
97
+ end
98
+
99
+ req = Net::HTTP::Post.new(uri.request_uri)
100
+ if @connection.container[:post_body_signing]
101
+ req.set_form_data(post_data)
102
+ end
103
+ else
104
+ req = Net::HTTP::Get.new(uri.request_uri)
105
+ end
106
+
107
+ @connection.sign!(http, req)
108
+
109
+ if post_data
110
+ resp = http.post(req.path, post_data, {'Content-type' =>
111
+ @connection.container[:content_type]})
112
+ check_for_json_error!(resp)
113
+ else
114
+ resp = http.get(req.path)
115
+ check_for_http_error!(resp)
116
+ end
117
+
118
+ return resp.body
119
+ end
120
+
121
+ # Generates a request body hash as outlined by the OAuth spec:
122
+ # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/1/spec.html
123
+ def request_body_hash(post_data)
124
+ CGI.escape(Base64.encode64(Digest::SHA1.digest(post_data)).rstrip)
125
+ end
126
+
127
+ # Checks the response object's status code. If the response is is
128
+ # unauthorized, an exception is raised.
129
+ def check_for_http_error!(resp)
130
+ if !resp.kind_of?(Net::HTTPSuccess)
131
+ if resp.is_a?(Net::HTTPUnauthorized)
132
+ raise AuthException.new('The request lacked proper authentication ' +
133
+ 'credentials to retrieve data.')
134
+ else
135
+ resp.value
136
+ end
137
+ end
138
+ end
139
+
140
+ # Checks the JSON response for a status code. If a code is present an
141
+ # exception is raised.
142
+ def check_for_json_error!(resp)
143
+ json = JSON.parse(resp.body)
144
+ if json.is_a?(Hash) && json.has_key?('code') && json.has_key?('message')
145
+ rc = json['code']
146
+ message = json['message']
147
+ case rc
148
+ when 401:
149
+ raise AuthException.new('The request lacked proper authentication ' +
150
+ 'credentials to retrieve data.')
151
+ else
152
+ raise RequestException.new("The request returned an unsupported " +
153
+ "status code: #{rc} #{message}.")
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ # Provides a wrapper for a single RPC request to an OpenSocial endpoint,
160
+ # composed of one or more individual requests.
161
+ #
162
+ # The RpcRequest class wraps an HTTP request to an OpenSocial endpoint for
163
+ # social data. An RpcRequest is intended to be used as a container for one
164
+ # or more Requests (or Fetch*Requests), but may also be used with a manually
165
+ # constructed post body. The RpcRequest class uses OAuth signing inherited
166
+ # from the Request class, when appropriate OAuth credentials are supplied.
167
+ #
168
+
169
+
170
+ class RpcRequest < Request
171
+
172
+ # Defines the requests sent in the single RpcRequest. The requests are
173
+ # stored a key/value pairs.
174
+ attr_accessor :requests
175
+
176
+ # Initializes an RpcRequest with the supplied connection and an optional
177
+ # hash of requests.
178
+ def initialize(connection, requests = {})
179
+ @connection = connection
180
+
181
+ @requests = requests
182
+ end
183
+
184
+ # Adds one or more requests to the RpcRequest. Expects a hash of key/value
185
+ # pairs (key used to refernece the data when it returns => the Request).
186
+ def add(requests = {})
187
+ @requests.merge!(requests)
188
+ end
189
+
190
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing JSON for
191
+ # the POST body and delegating the request to send_request. If an
192
+ # RpcRequest is sent with an empty list of requests, an exception is
193
+ # thrown. The response JSON is optionally unescaped (defaulting to true).
194
+ def send(unescape = true)
195
+ if @requests.length == 0
196
+ raise RequestException.new('RPC request requires a non-empty hash ' +
197
+ 'of requests in order to be sent.')
198
+ end
199
+
200
+ json = send_request(request_json, unescape)
201
+ end
202
+
203
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing the
204
+ # service URI and dispatching the request. This method is public so that
205
+ # an arbitrary POST body can be constructed and sent. The response JSON is
206
+ # optionally unescaped.
207
+ def send_request(post_data, unescape)
208
+ uri = @connection.service_uri(@connection.container[:rpc], nil, nil, nil)
209
+ data = dispatch(uri, post_data)
210
+
211
+ parse_response(data, unescape)
212
+ end
213
+
214
+ private
215
+
216
+ # Parses the response JSON. First, the JSON is unescaped, when specified,
217
+ # then for each element specified in @requests, the appropriate response
218
+ # is selected from the larger JSON response. This element is then delegated
219
+ # to the appropriate class to be turned into a native object (Person,
220
+ # Activity, etc.)
221
+ def parse_response(response, unescape)
222
+ if unescape
223
+ parsed = JSON.parse(response.os_unescape)
224
+ else
225
+ parsed = JSON.parse(response)
226
+ end
227
+ keyed_by_id = key_by_id(parsed)
228
+
229
+ native_objects = {}
230
+ @requests.each_pair do |key, request|
231
+ native_object = request.parse_rpc_response(keyed_by_id[key.to_s])
232
+ native_objects.merge!({key => native_object})
233
+ end
234
+
235
+ return native_objects
236
+ end
237
+
238
+ # Constructs a hash of the elements in data referencing each element
239
+ # by its 'id' attribute.
240
+ def key_by_id(data)
241
+ keyed_by_id = {}
242
+ for entry in data
243
+ keyed_by_id.merge!({entry['id'] => entry})
244
+ end
245
+
246
+ return keyed_by_id
247
+ end
248
+
249
+ # Modifies each request in an outgoing RpcRequest so that its key is set
250
+ # to the value specified when added to the RpcRequest.
251
+ def request_json
252
+ keyed_requests = []
253
+ @requests.each_pair do |key, request|
254
+ request.key = key
255
+ keyed_requests << request
256
+ end
257
+
258
+ return keyed_requests.to_json
259
+ end
260
+ end
261
+
262
+ # An exception thrown when a request cannot return data.
263
+ #
264
+
265
+
266
+ class RequestException < RuntimeError; end
267
+
268
+ # An exception thrown when a request returns a 401 unauthorized status.
269
+ #
270
+
271
+
272
+ class AuthException < RuntimeError; end
273
+ end