LEFT | RIGHT |
1 /* | 1 /* |
2 Copyright 2011 Google Inc | 2 Copyright 2011 Google Inc |
3 | 3 |
4 Licensed under the Apache License, Version 2.0 (the "License"); | 4 Licensed under the Apache License, Version 2.0 (the "License"); |
5 you may not use this file except in compliance with the License. | 5 you may not use this file except in compliance with the License. |
6 You may obtain a copy of the License at | 6 You may obtain a copy of the License at |
7 | 7 |
8 http://www.apache.org/licenses/LICENSE-2.0 | 8 http://www.apache.org/licenses/LICENSE-2.0 |
9 | 9 |
10 Unless required by applicable law or agreed to in writing, software | 10 Unless required by applicable law or agreed to in writing, software |
11 distributed under the License is distributed on an "AS IS" BASIS, | 11 distributed under the License is distributed on an "AS IS" BASIS, |
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 See the License for the specific language governing permissions and | 13 See the License for the specific language governing permissions and |
14 limitations under the License. | 14 limitations under the License. |
15 */ | 15 */ |
16 | 16 |
17 using System; | 17 using System; |
18 using System.Collections.Generic; | 18 using System.Collections.Generic; |
19 using System.IO; | 19 using System.IO; |
20 using System.Linq; | |
21 using System.Net.Http; | 20 using System.Net.Http; |
22 using System.Net.Http.Headers; | 21 using System.Net.Http.Headers; |
23 using System.Reflection; | |
24 using System.Threading; | 22 using System.Threading; |
25 using System.Threading.Tasks; | 23 using System.Threading.Tasks; |
26 | 24 |
27 using Google.Apis.Discovery; | 25 using Google.Apis.Discovery; |
28 using Google.Apis.Http; | 26 using Google.Apis.Http; |
29 using Google.Apis.Logging; | 27 using Google.Apis.Logging; |
30 using Google.Apis.Services; | 28 using Google.Apis.Services; |
31 using Google.Apis.Testing; | 29 using Google.Apis.Testing; |
32 using Google.Apis.Util; | 30 using Google.Apis.Util; |
33 using Google.Apis.Requests.Parameters; | 31 using Google.Apis.Requests.Parameters; |
34 | 32 |
35 namespace Google.Apis.Requests | 33 namespace Google.Apis.Requests |
36 { | 34 { |
37 /// <summary> | 35 /// <summary> |
38 /// Represents an abstract, strongly typed request base class to make reques
ts to a service. | 36 /// Represents an abstract, strongly typed request base class to make reques
ts to a service. |
39 /// Supports a strongly typed response. | 37 /// Supports a strongly typed response. |
40 /// </summary> | 38 /// </summary> |
41 /// <typeparam name="TResponse">The type of the response object</typeparam> | 39 /// <typeparam name="TResponse">The type of the response object</typeparam> |
42 public abstract class ClientServiceRequest<TResponse> : IClientServiceReques
t<TResponse> | 40 public abstract class ClientServiceRequest<TResponse> : IClientServiceReques
t<TResponse> |
43 { | 41 { |
44 /// <summary> The class logger </summary> | 42 /// <summary>The class logger.</summary> |
45 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<ClientServiceRequest<TResponse>>(); | 43 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<ClientServiceRequest<TResponse>>(); |
46 | 44 |
47 /// <summary> The service on which this request will be executed. </summ
ary> | 45 /// <summary>The service on which this request will be executed.</summar
y> |
48 private readonly IClientService service; | 46 private readonly IClientService service; |
49 | 47 |
50 /// <summary> Defines whether the E-Tag will be used in a specified way
or be ignored. </summary> | 48 /// <summary>Defines whether the E-Tag will be used in a specified way o
r be ignored.</summary> |
51 public ETagAction ETagAction { get; set; } | 49 public ETagAction ETagAction { get; set; } |
52 | 50 |
53 #region IClientServiceRequest Properties | 51 #region IClientServiceRequest Properties |
54 | 52 |
55 public abstract string MethodName { get; } | 53 public abstract string MethodName { get; } |
56 public abstract string RestPath { get; } | 54 public abstract string RestPath { get; } |
57 public abstract string HttpMethod { get; } | 55 public abstract string HttpMethod { get; } |
58 | 56 |
59 public IDictionary<string, IParameter> RequestParameters { get; private
set; } | 57 public IDictionary<string, IParameter> RequestParameters { get; private
set; } |
60 | 58 |
61 public IClientService Service | 59 public IClientService Service |
62 { | 60 { |
63 get { return service; } | 61 get { return service; } |
64 } | 62 } |
65 | 63 |
66 #endregion | 64 #endregion |
67 | 65 |
68 /// <summary> Creates a new service request. </summary> | 66 /// <summary>Creates a new service request.</summary> |
69 protected ClientServiceRequest(IClientService service) | 67 protected ClientServiceRequest(IClientService service) |
70 { | 68 { |
71 this.service = service; | 69 this.service = service; |
72 } | 70 } |
73 | 71 |
74 /// <summary> | 72 /// <summary> |
75 /// Initializes request's parameters. Inherited classes MUST override th
is method to add parameters to the | 73 /// Initializes request's parameters. Inherited classes MUST override th
is method to add parameters to the |
76 /// <see cref="RequestParameters"/> dictionary. | 74 /// <see cref="RequestParameters"/> dictionary. |
77 /// </summary> | 75 /// </summary> |
78 protected virtual void InitParameters() | 76 protected virtual void InitParameters() |
79 { | 77 { |
80 RequestParameters = new Dictionary<string, IParameter>(); | 78 RequestParameters = new Dictionary<string, IParameter>(); |
81 } | 79 } |
82 | 80 |
83 #region Execution | 81 #region Execution |
84 | 82 |
85 public TResponse Execute() | 83 public TResponse Execute() |
86 { | 84 { |
87 try | 85 try |
88 { | 86 { |
89 using (var response = ExecuteUnparsed(CancellationToken.None).Re
sult) | 87 using (var response = ExecuteUnparsed(CancellationToken.None).Re
sult) |
90 { | 88 { |
91 return ParseResponse(response).Result; | 89 return ParseResponse(response).Result; |
92 } | 90 } |
93 } | 91 } |
94 catch (AggregateException aex) | 92 catch (AggregateException aex) |
95 { | 93 { |
96 // if an exception was thrown during the tasks, unwrap and throw
it | 94 // If an exception was thrown during the tasks, unwrap and throw
it. |
97 throw aex.InnerException; | 95 throw aex.InnerException; |
98 } | 96 } |
99 catch (Exception ex) | 97 catch (Exception ex) |
100 { | 98 { |
101 throw ex; | 99 throw ex; |
102 } | 100 } |
103 } | 101 } |
104 | 102 |
105 public Stream ExecuteAsStream() | 103 public Stream ExecuteAsStream() |
106 { | 104 { |
107 // TODO(peleyal): should we copy the stream, and dispose the respons
e? | 105 // TODO(peleyal): should we copy the stream, and dispose the respons
e? |
108 try | 106 try |
109 { | 107 { |
110 // sync call | 108 // Sync call. |
111 var response = ExecuteUnparsed(CancellationToken.None).Result; | 109 var response = ExecuteUnparsed(CancellationToken.None).Result; |
112 return response.Content.ReadAsStreamAsync().Result; | 110 return response.Content.ReadAsStreamAsync().Result; |
113 } | 111 } |
114 catch (AggregateException aex) | 112 catch (AggregateException aex) |
115 { | 113 { |
116 // if an exception was thrown during the tasks, unwrap and throw
it | 114 // If an exception was thrown during the tasks, unwrap and throw
it. |
117 throw aex.InnerException; | 115 throw aex.InnerException; |
118 } | 116 } |
119 catch (Exception ex) | 117 catch (Exception ex) |
120 { | 118 { |
121 throw ex; | 119 throw ex; |
122 } | 120 } |
123 } | 121 } |
124 | 122 |
125 public async Task<TResponse> ExecuteAsync() | 123 public async Task<TResponse> ExecuteAsync() |
126 { | 124 { |
(...skipping 18 matching lines...) Expand all Loading... |
145 { | 143 { |
146 // TODO(peleyal): should we copy the stream, and dispose the respons
e? | 144 // TODO(peleyal): should we copy the stream, and dispose the respons
e? |
147 var response = await ExecuteAsyncUnparsed(cancellationToken).Configu
reAwait(false); | 145 var response = await ExecuteAsyncUnparsed(cancellationToken).Configu
reAwait(false); |
148 | 146 |
149 cancellationToken.ThrowIfCancellationRequested(); | 147 cancellationToken.ThrowIfCancellationRequested(); |
150 return await response.Content.ReadAsStreamAsync().ConfigureAwait(fal
se); | 148 return await response.Content.ReadAsStreamAsync().ConfigureAwait(fal
se); |
151 } | 149 } |
152 | 150 |
153 #region Helpers | 151 #region Helpers |
154 | 152 |
155 /// <summary> Sync executes the request without parsing the result. </su
mmary> | 153 /// <summary>Sync executes the request without parsing the result. </sum
mary> |
156 private async Task<HttpResponseMessage> ExecuteUnparsed(CancellationToke
n cancellationToken) | 154 private async Task<HttpResponseMessage> ExecuteUnparsed(CancellationToke
n cancellationToken) |
157 { | 155 { |
158 using (var request = CreateRequest()) | 156 using (var request = CreateRequest()) |
159 { | 157 { |
160 return await service.HttpClient.SendAsync(request, cancellationT
oken).ConfigureAwait(false); | 158 return await service.HttpClient.SendAsync(request, cancellationT
oken).ConfigureAwait(false); |
161 } | 159 } |
162 } | 160 } |
163 | 161 |
164 /// <summary> Async executes the request without parsing the result. </s
ummary> | 162 /// <summary>Async executes the request without parsing the result. </su
mmary> |
165 private Task<HttpResponseMessage> ExecuteAsyncUnparsed(CancellationToken
cancellationToken) | 163 private Task<HttpResponseMessage> ExecuteAsyncUnparsed(CancellationToken
cancellationToken) |
166 { | 164 { |
167 // TODO(peleyal): remove the creation of a new Task (it's not necess
ary). | 165 // TODO(peleyal): remove the creation of a new Task (it's not necess
ary). |
168 // It should also be removed from ResumableMediaUpload and MediaDown
loader! | 166 // It should also be removed from ResumableMediaUpload and MediaDown
loader! |
169 | |
170 // create a new task completion source and return its task. In addit
ional task we actually send the request | 167 // create a new task completion source and return its task. In addit
ional task we actually send the request |
171 // using ExecuteUnparsed and setting the result or the exception on
the completion source | 168 // using ExecuteUnparsed and setting the result or the exception on
the completion source |
172 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSo
urce<HttpResponseMessage>(); | 169 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSo
urce<HttpResponseMessage>(); |
173 Task.Factory.StartNew(async () => | 170 Task.Factory.StartNew(async () => |
174 { | 171 { |
175 try | 172 try |
176 { | 173 { |
177 var response = await ExecuteUnparsed(cancellationToken).
ConfigureAwait(false); | 174 var response = await ExecuteUnparsed(cancellationToken).
ConfigureAwait(false); |
178 tcs.SetResult(response); | 175 tcs.SetResult(response); |
179 } | 176 } |
180 catch (Exception ex) | 177 catch (Exception ex) |
181 { | 178 { |
182 // exception was thrown - it must be set on the task com
pletion source | 179 // Exception was thrown - it must be set on the task com
pletion source. |
183 tcs.SetException(ex); | 180 tcs.SetException(ex); |
184 } | 181 } |
185 }).ConfigureAwait(false); | 182 }).ConfigureAwait(false); |
186 return tcs.Task; | 183 return tcs.Task; |
187 } | 184 } |
188 | 185 |
189 /// <summary> Parses the response and deserialize the content into the r
equested response object. </summary> | 186 /// <summary>Parses the response and deserialize the content into the re
quested response object. </summary> |
190 private async Task<TResponse> ParseResponse(HttpResponseMessage response
) | 187 private async Task<TResponse> ParseResponse(HttpResponseMessage response
) |
191 { | 188 { |
192 if (response.IsSuccessStatusCode) | 189 if (response.IsSuccessStatusCode) |
193 { | 190 { |
194 return await service.DeserializeResponse<TResponse>(response).Co
nfigureAwait(false); | 191 return await service.DeserializeResponse<TResponse>(response).Co
nfigureAwait(false); |
195 } | 192 } |
196 var error = await service.DeserializeError(response).ConfigureAwait(
false); | 193 var error = await service.DeserializeError(response).ConfigureAwait(
false); |
197 throw new GoogleApiException(service.Name, error.ToString()); | 194 throw new GoogleApiException(service.Name, error.ToString()); |
198 } | 195 } |
199 | 196 |
200 #endregion | 197 #endregion |
201 | 198 |
202 #endregion | 199 #endregion |
203 | 200 |
204 /// <summary> | 201 /// <summary> |
205 /// Creates the <seealso cref="Google.Apis.Requests.RequestBuilder"/> wh
ich is used to generate a request. | 202 /// Creates the <seealso cref="Google.Apis.Requests.RequestBuilder"/> wh
ich is used to generate a request. |
206 /// </summary> | 203 /// </summary> |
207 /// <returns> | 204 /// <returns> |
208 /// A new builder instance which contains the HTTP method and the right
Uri with its path and query parameters. | 205 /// A new builder instance which contains the HTTP method and the right
Uri with its path and query parameters. |
209 /// </returns> | 206 /// </returns> |
210 private RequestBuilder CreateBuilder() | 207 private RequestBuilder CreateBuilder() |
211 { | 208 { |
212 var builder = new RequestBuilder() | 209 var builder = new RequestBuilder() |
213 { | 210 { |
214 BaseUri = new Uri(Service.BaseUri), | 211 BaseUri = new Uri(Service.BaseUri), |
215 Path = RestPath, | 212 Path = RestPath, |
216 Method = HttpMethod, | 213 Method = HttpMethod, |
217 }; | 214 }; |
218 | 215 |
219 // init parameters | 216 // Init parameters. |
220 builder.AddParameter(RequestParameterType.Query, "key", service.ApiK
ey); | 217 builder.AddParameter(RequestParameterType.Query, "key", service.ApiK
ey); |
221 var parameters = ParameterUtils.CreateParameterDictionary(this); | 218 var parameters = ParameterUtils.CreateParameterDictionary(this); |
222 AddParameters(builder, ParameterCollection.FromDictionary(parameters
)); | 219 AddParameters(builder, ParameterCollection.FromDictionary(parameters
)); |
223 return builder; | 220 return builder; |
224 } | 221 } |
225 | 222 |
226 /// <summary>Generates the right Url for this request.</summary> | 223 /// <summary>Generates the right URL for this request.</summary> |
227 protected string GenerateRequestUri() | 224 protected string GenerateRequestUri() |
228 { | 225 { |
229 return CreateBuilder().BuildUri().ToString(); | 226 return CreateBuilder().BuildUri().ToString(); |
230 } | 227 } |
231 | 228 |
232 /// <summary>Creates a HTTP request message with all class parameters, d
eveloper-key, ETag, etc.</summary> | 229 /// <summary>Creates a HTTP request message with all class parameters, d
eveloper-key, ETag, etc.</summary> |
233 [VisibleForTestOnly] | 230 [VisibleForTestOnly] |
234 internal HttpRequestMessage CreateRequest() | 231 internal HttpRequestMessage CreateRequest() |
235 { | 232 { |
236 var builder = CreateBuilder(); | 233 var builder = CreateBuilder(); |
237 var request = builder.CreateRequest(); | 234 var request = builder.CreateRequest(); |
238 object body = GetBody(); | 235 object body = GetBody(); |
239 if (body != null) | 236 if (body != null) |
240 { | 237 { |
241 service.SetRequestSerailizedContent(request, body); | 238 service.SetRequestSerailizedContent(request, body); |
242 } | 239 } |
243 | 240 |
244 AddETag(request); | 241 AddETag(request); |
245 return request; | 242 return request; |
246 } | 243 } |
247 | 244 |
248 protected virtual object GetBody() | 245 protected virtual object GetBody() |
249 { | 246 { |
250 return null; | 247 return null; |
251 } | 248 } |
252 | 249 |
253 #region ETag | 250 #region ETag |
254 | 251 |
255 /// <summary> | 252 /// <summary> |
256 /// Adds the right ETag action (e.g. If-Match) header to the given HTTP
request if the body contains ETag. | 253 /// Adds the right ETag action (e.g. If-Match) header to the given HTTP
request if the body contains ETag. |
257 /// </summary> | 254 /// </summary> |
258 private void AddETag(HttpRequestMessage request) | 255 private void AddETag(HttpRequestMessage request) |
259 { | 256 { |
260 IDirectResponseSchema body = GetBody() as IDirectResponseSchema; | 257 IDirectResponseSchema body = GetBody() as IDirectResponseSchema; |
261 if (body != null && !string.IsNullOrEmpty(body.ETag)) | 258 if (body != null && !string.IsNullOrEmpty(body.ETag)) |
262 { | 259 { |
263 ETagAction action = ETagAction == ETagAction.Default ? GetDefaul
tETagAction(HttpMethod) : ETagAction; | 260 ETagAction action = ETagAction == ETagAction.Default ? GetDefaul
tETagAction(HttpMethod) : ETagAction; |
264 switch (action) | 261 switch (action) |
265 { | 262 { |
266 case ETagAction.IfMatch: | 263 case ETagAction.IfMatch: |
267 request.Headers.IfMatch.Add(new EntityTagHeaderValue(bod
y.ETag)); | 264 request.Headers.IfMatch.Add(new EntityTagHeaderValue(bod
y.ETag)); |
268 break; | 265 break; |
269 case ETagAction.IfNoneMatch: | 266 case ETagAction.IfNoneMatch: |
270 request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue
(body.ETag)); | 267 request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue
(body.ETag)); |
271 break; | 268 break; |
272 } | 269 } |
273 } | 270 } |
274 } | 271 } |
275 | 272 |
276 /// <summary> | 273 /// <summary>Returns the default ETagAction for a specific HTTP verb.</s
ummary> |
277 /// Returns the default ETagAction for a specific HTTP verb. | |
278 /// </summary> | |
279 [VisibleForTestOnly] | 274 [VisibleForTestOnly] |
280 internal static ETagAction GetDefaultETagAction(string httpMethod) | 275 internal static ETagAction GetDefaultETagAction(string httpMethod) |
281 { | 276 { |
282 switch (httpMethod) | 277 switch (httpMethod) |
283 { | 278 { |
284 // Incoming data should only be updated if it has been changed o
n the server. | 279 // Incoming data should only be updated if it has been changed o
n the server. |
285 case HttpConsts.Get: | 280 case HttpConsts.Get: |
286 return ETagAction.IfNoneMatch; | 281 return ETagAction.IfNoneMatch; |
287 | 282 |
288 // Outgoing data should only be committed if it hasn't been chan
ged on the server. | 283 // Outgoing data should only be committed if it hasn't been chan
ged on the server. |
289 case HttpConsts.Put: | 284 case HttpConsts.Put: |
290 case HttpConsts.Post: | 285 case HttpConsts.Post: |
291 case HttpConsts.Patch: | 286 case HttpConsts.Patch: |
292 case HttpConsts.Delete: | 287 case HttpConsts.Delete: |
293 return ETagAction.IfMatch; | 288 return ETagAction.IfMatch; |
294 | 289 |
295 default: | 290 default: |
296 return ETagAction.Ignore; | 291 return ETagAction.Ignore; |
297 } | 292 } |
298 } | 293 } |
299 | 294 |
300 #endregion | 295 #endregion |
301 | 296 |
302 #region Parameters | 297 #region Parameters |
303 | 298 |
304 /// <summary> Adds path and query parameters to the given <c>requestBuil
der</c>. </summary> | 299 /// <summary>Adds path and query parameters to the given <c>requestBuild
er</c>.</summary> |
305 private void AddParameters(RequestBuilder requestBuilder, ParameterColle
ction inputParameters) | 300 private void AddParameters(RequestBuilder requestBuilder, ParameterColle
ction inputParameters) |
306 { | 301 { |
307 foreach (var parameter in inputParameters) | 302 foreach (var parameter in inputParameters) |
308 { | 303 { |
309 IParameter parameterDefinition; | 304 IParameter parameterDefinition; |
310 | 305 |
311 if (!RequestParameters.TryGetValue(parameter.Key, out parameterD
efinition)) | 306 if (!RequestParameters.TryGetValue(parameter.Key, out parameterD
efinition)) |
312 { | 307 { |
313 throw new GoogleApiException(Service.Name, | 308 throw new GoogleApiException(Service.Name, |
314 String.Format("Invalid parameter \"{0}\" was specified",
parameter.Key)); | 309 String.Format("Invalid parameter \"{0}\" was specified",
parameter.Key)); |
(...skipping 23 matching lines...) Expand all Loading... |
338 requestBuilder.AddParameter(RequestParameterType.Que
ry, parameter.Key, value); | 333 requestBuilder.AddParameter(RequestParameterType.Que
ry, parameter.Key, value); |
339 } | 334 } |
340 break; | 335 break; |
341 default: | 336 default: |
342 throw new GoogleApiException(service.Name, | 337 throw new GoogleApiException(service.Name, |
343 string.Format("Unsupported parameter type \"{0}\" fo
r \"{1}\"", | 338 string.Format("Unsupported parameter type \"{0}\" fo
r \"{1}\"", |
344 parameterDefinition.ParameterType, parameterDefiniti
on.Name)); | 339 parameterDefinition.ParameterType, parameterDefiniti
on.Name)); |
345 } | 340 } |
346 } | 341 } |
347 | 342 |
348 // check if there is a required parameter which wasn't set | 343 // Check if there is a required parameter which wasn't set. |
349 foreach (var parameter in RequestParameters.Values) | 344 foreach (var parameter in RequestParameters.Values) |
350 { | 345 { |
351 if (parameter.IsRequired && !inputParameters.ContainsKey(paramet
er.Name)) | 346 if (parameter.IsRequired && !inputParameters.ContainsKey(paramet
er.Name)) |
352 { | 347 { |
353 throw new GoogleApiException(service.Name, | 348 throw new GoogleApiException(service.Name, |
354 string.Format("Parameter \"{0}\" is missing", parameter.
Name)); | 349 string.Format("Parameter \"{0}\" is missing", parameter.
Name)); |
355 } | 350 } |
356 } | 351 } |
357 } | 352 } |
358 | 353 |
359 #endregion | 354 #endregion |
360 } | 355 } |
361 } | 356 } |
LEFT | RIGHT |