opensocial 0.0.4

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,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