PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Client.Silverlight/Connection/Async/AsyncServerClient.cs

http://github.com/ravendb/ravendb
C# | 1009 lines | 957 code | 14 blank | 38 comment | 2 complexity | 1edd7f483eaa60df2afbbaa8579d05b3 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. //-----------------------------------------------------------------------
  2. // <copyright file="AsyncServerClient.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System.Text;
  7. using Raven.Abstractions.Exceptions;
  8. using Raven.Abstractions.Extensions;
  9. using Raven.Abstractions.Json;
  10. using Raven.Client.Connection.Async;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Globalization;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Net;
  17. using System.Threading.Tasks;
  18. using Newtonsoft.Json;
  19. using Newtonsoft.Json.Linq;
  20. using Raven.Abstractions.Data;
  21. using Raven.Client.Connection;
  22. using Raven.Client.Connection.Profiling;
  23. using Raven.Client.Exceptions;
  24. using Raven.Client.Silverlight.Data;
  25. using Raven.Client.Document;
  26. using Raven.Abstractions.Commands;
  27. using Raven.Abstractions.Indexing;
  28. using Raven.Json.Linq;
  29. using Raven.Client.Extensions;
  30. #if !NET_3_5
  31. namespace Raven.Client.Silverlight.Connection.Async
  32. {
  33. /// <summary>
  34. /// Access the database commands in async fashion
  35. /// </summary>
  36. public class AsyncServerClient : IAsyncDatabaseCommands
  37. {
  38. private readonly string url;
  39. private readonly ICredentials credentials;
  40. private readonly HttpJsonRequestFactory jsonRequestFactory;
  41. private readonly Guid? sessionId;
  42. private readonly Task veryFirstRequest;
  43. private readonly DocumentConvention convention;
  44. private readonly ProfilingInformation profilingInformation;
  45. /// <summary>
  46. /// Get the current json request factory
  47. /// </summary>
  48. public HttpJsonRequestFactory JsonRequestFactory
  49. {
  50. get { return jsonRequestFactory; }
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="AsyncServerClient"/> class.
  54. /// </summary>
  55. public AsyncServerClient(string url, DocumentConvention convention, ICredentials credentials, HttpJsonRequestFactory jsonRequestFactory, Guid? sessionId, Task veryFirstRequest)
  56. {
  57. profilingInformation = ProfilingInformation.CreateProfilingInformation(sessionId);
  58. this.url = url.EndsWith("/") ? url.Substring(0, url.Length - 1) : url;
  59. this.convention = convention;
  60. this.credentials = credentials;
  61. this.jsonRequestFactory = jsonRequestFactory;
  62. this.sessionId = sessionId;
  63. this.veryFirstRequest = veryFirstRequest;
  64. jsonRequestFactory.ConfigureRequest += (sender, args) =>
  65. {
  66. args.JsonRequest.WaitForTask = veryFirstRequest;
  67. };
  68. }
  69. /// <summary>
  70. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  71. /// </summary>
  72. public void Dispose()
  73. {
  74. }
  75. public HttpJsonRequest CreateRequest(string relativeUrl, string method)
  76. {
  77. return jsonRequestFactory.CreateHttpJsonRequest(this, url + relativeUrl, method, credentials, convention);
  78. }
  79. /// <summary>
  80. /// Create a new instance of <see cref="IAsyncDatabaseCommands"/> that will interacts
  81. /// with the specified database
  82. /// </summary>
  83. public IAsyncDatabaseCommands ForDatabase(string database)
  84. {
  85. var databaseUrl = MultiDatabase.GetRootDatabaseUrl(url);
  86. databaseUrl = databaseUrl + "/databases/" + database + "/";
  87. return new AsyncServerClient(databaseUrl, convention, credentials, jsonRequestFactory, sessionId, veryFirstRequest)
  88. {
  89. operationsHeaders = operationsHeaders
  90. };
  91. }
  92. /// <summary>
  93. /// Create a new instance of <see cref="IAsyncDatabaseCommands"/> that will interacts
  94. /// with the default database. Useful if the database has works against a tenant database.
  95. /// </summary>
  96. public IAsyncDatabaseCommands ForDefaultDatabase()
  97. {
  98. var rootDatabaseUrl = MultiDatabase.GetRootDatabaseUrl(url);
  99. if (rootDatabaseUrl == url)
  100. return this;
  101. return new AsyncServerClient(rootDatabaseUrl, convention, credentials, jsonRequestFactory, sessionId, veryFirstRequest)
  102. {
  103. operationsHeaders = operationsHeaders
  104. };
  105. }
  106. /// <summary>
  107. /// Returns a new <see cref="IAsyncDatabaseCommands "/> using the specified credentials
  108. /// </summary>
  109. /// <param name="credentialsForSession">The credentials for session.</param>
  110. public IAsyncDatabaseCommands With(ICredentials credentialsForSession)
  111. {
  112. return new AsyncServerClient(url, convention, credentialsForSession, jsonRequestFactory, sessionId, veryFirstRequest);
  113. }
  114. private IDictionary<string, string> operationsHeaders = new Dictionary<string, string>();
  115. /// <summary>
  116. /// Gets or sets the operations headers.
  117. /// </summary>
  118. /// <value>The operations headers.</value>
  119. public IDictionary<string, string> OperationsHeaders
  120. {
  121. get { return operationsHeaders; }
  122. }
  123. /// <summary>
  124. /// Begins an async get operation
  125. /// </summary>
  126. /// <param name="key">The key.</param>
  127. /// <returns></returns>
  128. public Task<JsonDocument> GetAsync(string key)
  129. {
  130. EnsureIsNotNullOrEmpty(key, "key");
  131. key = key.Replace("\\", @"/"); //NOTE: the present of \ causes the SL networking stack to barf, even though the Uri seemingly makes this translation itself
  132. var request = url.Docs(key)
  133. .NoCache()
  134. .ToJsonRequest(this, credentials, convention);
  135. return request
  136. .ReadResponseJsonAsync()
  137. .ContinueWith(task =>
  138. {
  139. try
  140. {
  141. var token = task.Result;
  142. var docKey = key;
  143. IList<string> list;
  144. if(request.ResponseHeaders.TryGetValue(Constants.DocumentIdFieldName, out list))
  145. {
  146. docKey = list.FirstOrDefault() ?? key;
  147. request.ResponseHeaders.Remove(Constants.DocumentIdFieldName);
  148. }
  149. return new JsonDocument
  150. {
  151. DataAsJson = (RavenJObject)token,
  152. NonAuthoritativeInformation = request.ResponseStatusCode == HttpStatusCode.NonAuthoritativeInformation,
  153. Key = docKey,
  154. LastModified = DateTime.ParseExact(request.ResponseHeaders[Constants.LastModified].First(), "r", CultureInfo.InvariantCulture).ToLocalTime(),
  155. Etag = new Guid(request.ResponseHeaders["ETag"].First()),
  156. Metadata = request.ResponseHeaders.FilterHeaders(isServerDocument: false)
  157. };
  158. }
  159. catch (AggregateException e)
  160. {
  161. var webException = e.ExtractSingleInnerException() as WebException;
  162. if (webException != null)
  163. {
  164. if (HandleWebExceptionForGetAsync(key, webException))
  165. return null;
  166. }
  167. throw;
  168. }
  169. catch (WebException e)
  170. {
  171. if (HandleWebExceptionForGetAsync(key, e))
  172. return null;
  173. throw;
  174. }
  175. });
  176. }
  177. private static bool HandleWebExceptionForGetAsync(string key, WebException e)
  178. {
  179. var httpWebResponse = e.Response as HttpWebResponse;
  180. if (httpWebResponse == null)
  181. {
  182. return false;
  183. }
  184. if (httpWebResponse.StatusCode == HttpStatusCode.NotFound)
  185. {
  186. return true;
  187. }
  188. if (httpWebResponse.StatusCode == HttpStatusCode.Conflict)
  189. {
  190. var conflicts = new StreamReader(httpWebResponse.GetResponseStream());
  191. var conflictsDoc = RavenJObject.Load(new JsonTextReader(conflicts));
  192. var conflictIds = conflictsDoc.Value<RavenJArray>("Conflicts").Select(x => x.Value<string>()).ToArray();
  193. throw new ConflictException("Conflict detected on " + key +
  194. ", conflict must be resolved before the document will be accessible")
  195. {
  196. ConflictedVersionIds = conflictIds,
  197. Etag = new Guid(httpWebResponse.Headers["ETag"])
  198. };
  199. }
  200. return false;
  201. }
  202. private T AttemptToProcessResponse<T>(Func<T> process) where T : class
  203. {
  204. try
  205. {
  206. return process();
  207. }
  208. catch (AggregateException e)
  209. {
  210. var webException = e.ExtractSingleInnerException() as WebException;
  211. if (webException == null) throw;
  212. if (HandleException(webException)) return null;
  213. throw;
  214. }
  215. catch (WebException e)
  216. {
  217. if (HandleException(e)) return null;
  218. throw;
  219. }
  220. }
  221. /// <summary>
  222. /// Attempts to handle an exception raised when receiving a response from the server
  223. /// </summary>
  224. /// <param name="e">The exception to handle</param>
  225. /// <returns>returns true if the exception is handled, false if it should be thrown</returns>
  226. private bool HandleException(WebException e)
  227. {
  228. var httpWebResponse = e.Response as HttpWebResponse;
  229. if (httpWebResponse == null)
  230. {
  231. return false;
  232. }
  233. if (httpWebResponse.StatusCode == HttpStatusCode.InternalServerError)
  234. {
  235. var content = new StreamReader(httpWebResponse.GetResponseStream());
  236. var jo = RavenJObject.Load(new JsonTextReader(content));
  237. var error = jo.Deserialize<ServerRequestError>(convention);
  238. throw new WebException(error.Error);
  239. }
  240. return false;
  241. }
  242. /// <summary>
  243. /// Perform a single POST request containing multiple nested GET requests
  244. /// </summary>
  245. public Task<GetResponse[]> MultiGetAsync(GetRequest[] requests)
  246. {
  247. var postedData = JsonConvert.SerializeObject(requests);
  248. var httpJsonRequest = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/multi_get/", "POST",
  249. credentials, convention);
  250. return httpJsonRequest.WriteAsync(postedData)
  251. .ContinueWith(
  252. task =>
  253. {
  254. task.Wait();// will throw if write errored
  255. return httpJsonRequest.ReadResponseJsonAsync()
  256. .ContinueWith(replyTask => convention.CreateSerializer().Deserialize<GetResponse[]>(new RavenJTokenReader(replyTask.Result)));
  257. })
  258. .Unwrap();
  259. }
  260. public Task<LogItem[]> GetLogsAsync(bool errorsOnly)
  261. {
  262. var requestUri = url + "/logs";
  263. if (errorsOnly)
  264. requestUri += "?type=error";
  265. var request = jsonRequestFactory.CreateHttpJsonRequest(this, requestUri.NoCache(), "GET", credentials, convention);
  266. request.AddOperationHeaders(OperationsHeaders);
  267. return request.ReadResponseJsonAsync()
  268. .ContinueWith(task => convention.CreateSerializer().Deserialize<LogItem[]>(new RavenJTokenReader(task.Result)));
  269. }
  270. public Task<LicensingStatus> GetLicenseStatus()
  271. {
  272. var request = jsonRequestFactory.CreateHttpJsonRequest(this, (url + "/license/status").NoCache(), "GET", credentials, convention);
  273. request.AddOperationHeaders(OperationsHeaders);
  274. return request.ReadResponseJsonAsync()
  275. .ContinueWith(task => convention.CreateSerializer().Deserialize<LicensingStatus>(new RavenJTokenReader(task.Result)));
  276. }
  277. public Task StartBackupAsync(string backupLocation)
  278. {
  279. var request = jsonRequestFactory.CreateHttpJsonRequest(this, (url + "/admin/backup").NoCache(), "POST", credentials, convention);
  280. request.AddOperationHeaders(OperationsHeaders);
  281. return request.WriteAsync(new RavenJObject
  282. {
  283. {"BackupLocation", backupLocation}
  284. }.ToString(Formatting.None))
  285. .ContinueWith(task =>
  286. {
  287. if (task.Exception != null)
  288. return task;
  289. return request.ExecuteRequest();
  290. }).Unwrap();
  291. }
  292. public Task<JsonDocument[]> StartsWithAsync(string keyPrefix, int start, int pageSize)
  293. {
  294. var metadata = new RavenJObject();
  295. var actualUrl = string.Format("{0}/docs?startsWith={1}&start={2}&pageSize={3}", url, Uri.EscapeDataString(keyPrefix), start, pageSize);
  296. var request = jsonRequestFactory.CreateHttpJsonRequest(this, actualUrl, "GET", metadata, credentials, convention);
  297. request.AddOperationHeaders(OperationsHeaders);
  298. return request.ReadResponseJsonAsync()
  299. .ContinueWith(task => SerializationHelper.RavenJObjectsToJsonDocuments(((RavenJArray)task.Result).OfType<RavenJObject>()).ToArray());
  300. }
  301. public Task<BuildNumber> GetBuildNumber()
  302. {
  303. var request = jsonRequestFactory.CreateHttpJsonRequest(this, (url + "/build/version").NoCache(), "GET", credentials, convention);
  304. request.AddOperationHeaders(OperationsHeaders);
  305. return request.ReadResponseJsonAsync()
  306. .ContinueWith(task => convention.CreateSerializer().Deserialize<BuildNumber>(new RavenJTokenReader(task.Result)));
  307. }
  308. /// <summary>
  309. /// Using the given Index, calculate the facets as per the specified doc
  310. /// </summary>
  311. public Task<IDictionary<string, IEnumerable<FacetValue>>> GetFacetsAsync(string index, IndexQuery query, string facetSetupDoc)
  312. {
  313. var requestUri = url + string.Format("/facets/{0}?facetDoc={1}&query={2}",
  314. Uri.EscapeUriString(index),
  315. Uri.EscapeDataString(facetSetupDoc),
  316. Uri.EscapeDataString(query.Query));
  317. var request = jsonRequestFactory.CreateHttpJsonRequest(this, requestUri.NoCache(), "GET", credentials, convention);
  318. request.AddOperationHeaders(OperationsHeaders);
  319. return request.ReadResponseJsonAsync()
  320. .ContinueWith(task =>
  321. {
  322. var json = (RavenJObject)task.Result;
  323. return json.JsonDeserialization<IDictionary<string, IEnumerable<FacetValue>>>();
  324. });
  325. }
  326. /// <summary>
  327. /// Begins an async multi get operation
  328. /// </summary>
  329. public Task<MultiLoadResult> GetAsync(string[] keys, string[] includes)
  330. {
  331. var path = url + "/queries/?";
  332. if (includes != null && includes.Length > 0)
  333. {
  334. path += string.Join("&", includes.Select(x => "include=" + x).ToArray());
  335. }
  336. // if it is too big, we drop to POST (note that means that we can't use the HTTP cache any longer)
  337. // we are fine with that, requests to load > 128 items are going to be rare
  338. HttpJsonRequest request;
  339. if (keys.Length < 128)
  340. {
  341. path += "&" + string.Join("&", keys.Select(x => "id=" + x).ToArray());
  342. request = jsonRequestFactory.CreateHttpJsonRequest(this, path.NoCache(), "GET", credentials, convention);
  343. return request.ReadResponseJsonAsync()
  344. .ContinueWith(task => CompleteMultiGet(task));
  345. }
  346. request = jsonRequestFactory.CreateHttpJsonRequest(this, path, "POST", credentials, convention);
  347. return request.WriteAsync(new JArray(keys).ToString(Formatting.None))
  348. .ContinueWith(writeTask => request.ReadResponseJsonAsync())
  349. .ContinueWith(task => CompleteMultiGet(task.Result));
  350. }
  351. private static MultiLoadResult CompleteMultiGet(Task<RavenJToken> task)
  352. {
  353. try
  354. {
  355. var result = (RavenJObject)task.Result;
  356. return new MultiLoadResult
  357. {
  358. Includes = result.Value<RavenJArray>("Includes").Cast<RavenJObject>().ToList(),
  359. Results = result.Value<RavenJArray>("Results").Cast<RavenJObject>().ToList()
  360. };
  361. }
  362. catch (WebException e)
  363. {
  364. var httpWebResponse = e.Response as HttpWebResponse;
  365. if (httpWebResponse == null ||
  366. httpWebResponse.StatusCode != HttpStatusCode.Conflict)
  367. throw;
  368. throw ThrowConcurrencyException(e);
  369. }
  370. }
  371. /// <summary>
  372. /// Begins an async get operation for documents
  373. /// </summary>
  374. /// <param name="start">Paging start</param>
  375. /// <param name="pageSize">Size of the page.</param>
  376. /// <remarks>
  377. /// This is primarily useful for administration of a database
  378. /// </remarks>
  379. public Task<JsonDocument[]> GetDocumentsAsync(int start, int pageSize)
  380. {
  381. return url.Docs(start, pageSize)
  382. .NoCache()
  383. .ToJsonRequest(this, credentials, convention)
  384. .ReadResponseJsonAsync()
  385. .ContinueWith(task => ((RavenJArray)task.Result)
  386. .Cast<RavenJObject>()
  387. .ToJsonDocuments()
  388. .ToArray());
  389. }
  390. /// <summary>
  391. /// Begins an async get operation for documents whose id starts with the specified prefix
  392. /// </summary>
  393. /// <param name="prefix">Prefix that the ids begin with.</param>
  394. /// <param name="start">Paging start.</param>
  395. /// <param name="pageSize">Size of the page.</param>
  396. /// <remarks>
  397. /// This is primarily useful for administration of a database
  398. /// </remarks>
  399. public Task<JsonDocument[]> GetDocumentsStartingWithAsync(string prefix, int start, int pageSize)
  400. {
  401. return url.DocsStartingWith(prefix, start, pageSize)
  402. .NoCache()
  403. .ToJsonRequest(this, credentials, convention)
  404. .ReadResponseJsonAsync()
  405. .ContinueWith(task => ((RavenJArray)task.Result)
  406. .Cast<RavenJObject>()
  407. .ToJsonDocuments()
  408. .ToArray());
  409. }
  410. /// <summary>
  411. /// Begins the async query.
  412. /// </summary>
  413. /// <param name="index">The index.</param>
  414. /// <param name="query">The query.</param>
  415. /// <param name="includes">The include paths</param>
  416. /// <returns></returns>
  417. public Task<QueryResult> QueryAsync(string index, IndexQuery query, string[] includes)
  418. {
  419. EnsureIsNotNullOrEmpty(index, "index");
  420. var path = query.GetIndexQueryUrl(url, index, "indexes");
  421. if (includes != null && includes.Length > 0)
  422. {
  423. path += "&" + string.Join("&", includes.Select(x => "include=" + x).ToArray());
  424. }
  425. var request = jsonRequestFactory.CreateHttpJsonRequest(this, path.NoCache(), "GET", credentials, convention);
  426. return request.ReadResponseJsonAsync()
  427. .ContinueWith(task => AttemptToProcessResponse(() => SerializationHelper.ToQueryResult((RavenJObject)task.Result, request.ResponseHeaders["ETag"].First())));
  428. }
  429. public Task DeleteByIndexAsync(string indexName, IndexQuery queryToDelete, bool allowStale)
  430. {
  431. string path = queryToDelete.GetIndexQueryUrl(url, indexName, "bulk_docs") + "&allowStale=" + allowStale;
  432. var request = jsonRequestFactory.CreateHttpJsonRequest(this, path, "DELETE", credentials, convention);
  433. request.AddOperationHeaders(OperationsHeaders);
  434. return request.ExecuteRequest()
  435. .ContinueWith(task =>
  436. {
  437. var aggregateException = task.Exception;
  438. if (aggregateException == null)
  439. return task;
  440. var e = aggregateException.ExtractSingleInnerException() as WebException;
  441. if (e == null)
  442. return task;
  443. var httpWebResponse = e.Response as HttpWebResponse;
  444. if (httpWebResponse != null && httpWebResponse.StatusCode == HttpStatusCode.NotFound)
  445. throw new InvalidOperationException("There is no index named: " + indexName, e);
  446. return task;
  447. }).Unwrap();
  448. }
  449. /// <summary>
  450. /// Deletes the document for the specified id asynchronously
  451. /// </summary>
  452. /// <param name="id">The id.</param>
  453. public Task DeleteDocumentAsync(string id)
  454. {
  455. return url.Docs(id)
  456. .ToJsonRequest(this, credentials, convention, OperationsHeaders, "DELETE")
  457. .ExecuteRequest();
  458. }
  459. /// <summary>
  460. /// Puts the document with the specified key in the database
  461. /// </summary>
  462. /// <param name="key">The key.</param>
  463. /// <param name="etag">The etag.</param>
  464. /// <param name="document">The document.</param>
  465. /// <param name="metadata">The metadata.</param>
  466. public Task<PutResult> PutAsync(string key, Guid? etag, RavenJObject document, RavenJObject metadata)
  467. {
  468. if (metadata == null)
  469. metadata = new RavenJObject();
  470. var method = String.IsNullOrEmpty(key) ? "POST" : "PUT";
  471. if (etag != null)
  472. metadata["ETag"] = new RavenJValue(etag.Value.ToString());
  473. var request = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/docs/" + key, method, metadata, credentials, convention);
  474. request.AddOperationHeaders(OperationsHeaders);
  475. return request.WriteAsync(document.ToString())
  476. .ContinueWith(task =>
  477. {
  478. if (task.Exception != null)
  479. throw new InvalidOperationException("Unable to write to server", task.Exception);
  480. return request.ReadResponseJsonAsync()
  481. .ContinueWith(task1 =>
  482. {
  483. try
  484. {
  485. return convention.CreateSerializer().Deserialize<PutResult>(new RavenJTokenReader(task1.Result));
  486. }
  487. catch (AggregateException e)
  488. {
  489. var webexception = e.ExtractSingleInnerException() as WebException;
  490. if (ShouldThrowForPutAsync(webexception)) throw;
  491. throw ThrowConcurrencyException(webexception);
  492. }
  493. catch (WebException e)
  494. {
  495. if (ShouldThrowForPutAsync(e)) throw;
  496. throw ThrowConcurrencyException(e);
  497. }
  498. });
  499. })
  500. .Unwrap();
  501. }
  502. static bool ShouldThrowForPutAsync(WebException e)
  503. {
  504. if (e == null) return true;
  505. var httpWebResponse = e.Response as HttpWebResponse;
  506. return (httpWebResponse == null ||
  507. httpWebResponse.StatusCode != HttpStatusCode.Conflict);
  508. }
  509. /// <summary>
  510. /// Gets the index definition for the specified name asynchronously
  511. /// </summary>
  512. /// <param name="name">The name.</param>
  513. public Task<IndexDefinition> GetIndexAsync(string name)
  514. {
  515. return url.IndexDefinition(name)
  516. .NoCache()
  517. .ToJsonRequest(this, credentials, convention).ReadResponseJsonAsync()
  518. .ContinueWith(task =>
  519. {
  520. var json = (RavenJObject)task.Result;
  521. //NOTE: To review, I'm not confidence this is the correct way to deserialize the index definition
  522. return convention.CreateSerializer().Deserialize<IndexDefinition>(new RavenJTokenReader(json["Index"]));
  523. });
  524. }
  525. /// <summary>
  526. /// Puts the index definition for the specified name asynchronously
  527. /// </summary>
  528. /// <param name="name">The name.</param>
  529. /// <param name="indexDef">The index def.</param>
  530. /// <param name="overwrite">Should overwrite index</param>
  531. public Task<string> PutIndexAsync(string name, IndexDefinition indexDef, bool overwrite)
  532. {
  533. string requestUri = url + "/indexes/" + Uri.EscapeUriString(name) + "?definition=yes";
  534. var webRequest = requestUri
  535. .ToJsonRequest(this, credentials, convention, OperationsHeaders, "GET");
  536. return webRequest.ExecuteRequest()
  537. .ContinueWith(task =>
  538. {
  539. try
  540. {
  541. task.Wait(); // should throw if it is bad
  542. if (overwrite == false)
  543. throw new InvalidOperationException("Cannot put index: " + name + ", index already exists");
  544. }
  545. catch (AggregateException e)
  546. {
  547. var webException = e.ExtractSingleInnerException() as WebException;
  548. if (ShouldThrowForPutIndexAsync(webException))
  549. throw;
  550. }
  551. catch (WebException e)
  552. {
  553. if (ShouldThrowForPutIndexAsync(e))
  554. throw;
  555. }
  556. var request = jsonRequestFactory.CreateHttpJsonRequest(this, requestUri, "PUT", credentials, convention);
  557. request.AddOperationHeaders(OperationsHeaders);
  558. var serializeObject = JsonConvert.SerializeObject(indexDef, new JsonEnumConverter());
  559. return request
  560. .WriteAsync(serializeObject)
  561. .ContinueWith(writeTask => AttemptToProcessResponse(() => request
  562. .ReadResponseJsonAsync()
  563. .ContinueWith(readStrTask => AttemptToProcessResponse(() =>
  564. {
  565. //NOTE: JsonConvert.DeserializeAnonymousType() doesn't work in Silverlight because the ctor is private!
  566. var obj = convention.CreateSerializer().Deserialize<IndexContainer>(new RavenJTokenReader(readStrTask.Result));
  567. return obj.Index;
  568. })))
  569. ).Unwrap();
  570. }).Unwrap();
  571. }
  572. /// <summary>
  573. /// Used for deserialization only :-P
  574. /// </summary>
  575. public class IndexContainer
  576. {
  577. public string Index { get; set; }
  578. }
  579. /// <summary>
  580. /// Deletes the index definition for the specified name asynchronously
  581. /// </summary>
  582. /// <param name="name">The name.</param>
  583. public Task DeleteIndexAsync(string name)
  584. {
  585. return url.Indexes(name)
  586. .ToJsonRequest(this, credentials, convention, OperationsHeaders, "DELETE")
  587. .ExecuteRequest();
  588. }
  589. private static bool ShouldThrowForPutIndexAsync(WebException e)
  590. {
  591. if (e == null) return true;
  592. var response = e.Response as HttpWebResponse;
  593. return (response == null || response.StatusCode != HttpStatusCode.NotFound);
  594. }
  595. /// <summary>
  596. /// Gets the index names from the server asynchronously
  597. /// </summary>
  598. /// <param name="start">Paging start</param>
  599. /// <param name="pageSize">Size of the page.</param>
  600. public Task<string[]> GetIndexNamesAsync(int start, int pageSize)
  601. {
  602. return url.IndexNames(start, pageSize)
  603. .NoCache()
  604. .ToJsonRequest(this, credentials, convention)
  605. .ReadResponseJsonAsync()
  606. .ContinueWith(task =>
  607. {
  608. var json = ((RavenJArray)task.Result);
  609. return json.Select(x => x.Value<string>()).ToArray();
  610. });
  611. }
  612. /// <summary>
  613. /// Gets the indexes from the server asynchronously
  614. /// </summary>
  615. /// <param name="start">Paging start</param>
  616. /// <param name="pageSize">Size of the page.</param>
  617. public Task<IndexDefinition[]> GetIndexesAsync(int start, int pageSize)
  618. {
  619. var url2 = (url + "/indexes/?start=" + start + "&pageSize=" + pageSize).NoCache();
  620. var request = jsonRequestFactory.CreateHttpJsonRequest(this, url2, "GET", credentials, convention);
  621. return request.ReadResponseJsonAsync()
  622. .ContinueWith(task =>
  623. {
  624. var json = ((RavenJArray)task.Result);
  625. //NOTE: To review, I'm not confidence this is the correct way to deserialize the index definition
  626. return json
  627. .Select(x => JsonConvert.DeserializeObject<IndexDefinition>(((RavenJObject)x)["definition"].ToString(), new JsonToJsonConverter()))
  628. .ToArray();
  629. });
  630. }
  631. /// <summary>
  632. /// Resets the specified index asynchronously
  633. /// </summary>
  634. /// <param name="name">The name.</param>
  635. public Task ResetIndexAsync(string name)
  636. {
  637. var httpJsonRequestAsync = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/indexes/" + name, "RESET", credentials, convention);
  638. httpJsonRequestAsync.AddOperationHeaders(OperationsHeaders);
  639. return httpJsonRequestAsync.ReadResponseJsonAsync();
  640. }
  641. /// <summary>
  642. /// Returns a list of suggestions based on the specified suggestion query.
  643. /// </summary>
  644. /// <param name="index">The index to query for suggestions</param>
  645. /// <param name="suggestionQuery">The suggestion query.</param>
  646. public Task<SuggestionQueryResult> SuggestAsync(string index, SuggestionQuery suggestionQuery)
  647. {
  648. if (suggestionQuery == null) throw new ArgumentNullException("suggestionQuery");
  649. var requestUri = url + string.Format("/suggest/{0}?term={1}&field={2}&max={3}&distance={4}&accuracy={5}",
  650. Uri.EscapeUriString(index),
  651. Uri.EscapeDataString(suggestionQuery.Term),
  652. Uri.EscapeDataString(suggestionQuery.Field),
  653. Uri.EscapeDataString(suggestionQuery.MaxSuggestions.ToString()),
  654. Uri.EscapeDataString(suggestionQuery.Distance.ToString()),
  655. Uri.EscapeDataString(suggestionQuery.Accuracy.ToString()));
  656. var request = jsonRequestFactory.CreateHttpJsonRequest(this, requestUri.NoCache(), "GET", credentials, convention);
  657. request.AddOperationHeaders(OperationsHeaders);
  658. return request.ReadResponseJsonAsync()
  659. .ContinueWith(task =>
  660. {
  661. var json = (RavenJObject)task.Result;
  662. return new SuggestionQueryResult
  663. {
  664. Suggestions = ((RavenJArray)json["Suggestions"]).Select(x => x.Value<string>()).ToArray(),
  665. };
  666. });
  667. }
  668. /// <summary>
  669. /// Begins the async batch operation
  670. /// </summary>
  671. /// <param name="commandDatas">The command data.</param>
  672. /// <returns></returns>
  673. public Task<BatchResult[]> BatchAsync(ICommandData[] commandDatas)
  674. {
  675. var metadata = new RavenJObject();
  676. var req = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/bulk_docs", "POST", metadata, credentials, convention);
  677. var jArray = new RavenJArray(commandDatas.Select(x => x.ToJson()));
  678. return req.WriteAsync(jArray.ToString(Formatting.None))
  679. .ContinueWith(writeTask => req.ReadResponseJsonAsync())
  680. .Unwrap()
  681. .ContinueWith(task =>
  682. {
  683. RavenJToken response;
  684. try
  685. {
  686. response = task.Result;
  687. }
  688. catch (WebException e)
  689. {
  690. var httpWebResponse = e.Response as HttpWebResponse;
  691. if (httpWebResponse == null ||
  692. httpWebResponse.StatusCode != HttpStatusCode.Conflict)
  693. throw;
  694. throw ThrowConcurrencyException(e);
  695. }
  696. return convention.CreateSerializer().Deserialize<BatchResult[]>(new RavenJTokenReader(response));
  697. });
  698. }
  699. public class ConcurrencyExceptionResult
  700. {
  701. private readonly string url1;
  702. private readonly Guid actualETag1;
  703. private readonly Guid expectedETag1;
  704. private readonly string error1;
  705. public string url
  706. {
  707. get { return url1; }
  708. }
  709. public Guid actualETag
  710. {
  711. get { return actualETag1; }
  712. }
  713. public Guid expectedETag
  714. {
  715. get { return expectedETag1; }
  716. }
  717. public string error
  718. {
  719. get { return error1; }
  720. }
  721. public ConcurrencyExceptionResult(string url, Guid actualETag, Guid expectedETag, string error)
  722. {
  723. url1 = url;
  724. actualETag1 = actualETag;
  725. expectedETag1 = expectedETag;
  726. error1 = error;
  727. }
  728. }
  729. private static Exception ThrowConcurrencyException(WebException e)
  730. {
  731. using (var sr = new StreamReader(e.Response.GetResponseStream()))
  732. {
  733. var text = sr.ReadToEnd();
  734. var errorResults = JsonConvert.DeserializeAnonymousType(text, new ConcurrencyExceptionResult((string)null, Guid.Empty, Guid.Empty, (string)null));
  735. return new ConcurrencyException(errorResults.error)
  736. {
  737. ActualETag = errorResults.actualETag,
  738. ExpectedETag = errorResults.expectedETag
  739. };
  740. }
  741. }
  742. private static void EnsureIsNotNullOrEmpty(string key, string argName)
  743. {
  744. if (string.IsNullOrEmpty(key))
  745. throw new ArgumentException("Key cannot be null or empty", argName);
  746. }
  747. /// <summary>
  748. /// Begins retrieving the statistics for the database
  749. /// </summary>
  750. /// <returns></returns>
  751. public Task<DatabaseStatistics> GetStatisticsAsync()
  752. {
  753. return url.Stats()
  754. .NoCache()
  755. .ToJsonRequest(this, credentials, convention)
  756. .ReadResponseJsonAsync()
  757. .ContinueWith(task =>
  758. {
  759. var jo = ((RavenJObject)task.Result);
  760. return jo.Deserialize<DatabaseStatistics>(convention);
  761. });
  762. }
  763. /// <summary>
  764. /// Gets the list of databases from the server asynchronously
  765. /// </summary>
  766. public Task<string[]> GetDatabaseNamesAsync(int pageSize)
  767. {
  768. return url.Databases(pageSize)
  769. .NoCache()
  770. .ToJsonRequest(this, credentials, convention)
  771. .ReadResponseJsonAsync()
  772. .ContinueWith(task =>
  773. {
  774. var json = ((RavenJArray)task.Result);
  775. return json
  776. .Select(x => x.Value<RavenJObject>("@metadata").Value<string>("@id").Replace("Raven/Databases/", string.Empty))
  777. .ToArray();
  778. });
  779. }
  780. /// <summary>
  781. /// Puts the attachment with the specified key asynchronously
  782. /// </summary>
  783. /// <param name="key">The key.</param>
  784. /// <param name="etag">The etag.</param>
  785. /// <param name="data">The data.</param>
  786. /// <param name="metadata">The metadata.</param>
  787. public Task PutAttachmentAsync(string key, Guid? etag, byte[] data, RavenJObject metadata)
  788. {
  789. if (metadata == null)
  790. metadata = new RavenJObject();
  791. if (etag != null)
  792. metadata["ETag"] = new RavenJValue(etag.Value.ToString());
  793. var request = jsonRequestFactory.CreateHttpJsonRequest(this, url.Static(key), "PUT", metadata, credentials, convention);
  794. request.AddOperationHeaders(OperationsHeaders);
  795. return request
  796. .WriteAsync(data)
  797. .ContinueWith(write =>
  798. {
  799. if (write.Exception != null)
  800. throw new InvalidOperationException("Unable to write to server");
  801. return request.ExecuteRequest();
  802. }).Unwrap();
  803. }
  804. /// <summary>
  805. /// Gets the attachment by the specified key asynchronously
  806. /// </summary>
  807. /// <param name="key">The key.</param>
  808. /// <returns></returns>
  809. public Task<Attachment> GetAttachmentAsync(string key)
  810. {
  811. EnsureIsNotNullOrEmpty(key, "key");
  812. var request = url.Static(key)
  813. .ToJsonRequest(this, credentials, convention);
  814. return request
  815. .ReadResponseBytesAsync()
  816. .ContinueWith(task =>
  817. {
  818. try
  819. {
  820. var buffer = task.Result;
  821. return new Attachment
  822. {
  823. Data = () => new MemoryStream(buffer),
  824. Etag = new Guid(request.ResponseHeaders["ETag"].First()),
  825. Metadata = request.ResponseHeaders.FilterHeaders(isServerDocument: false)
  826. };
  827. }
  828. catch (AggregateException e)
  829. {
  830. var webException = e.ExtractSingleInnerException() as WebException;
  831. if (webException != null)
  832. {
  833. if (HandleWebExceptionForGetAsync(key, webException))
  834. return null;
  835. }
  836. throw;
  837. }
  838. catch (WebException e)
  839. {
  840. if (HandleWebExceptionForGetAsync(key, e))
  841. return null;
  842. throw;
  843. }
  844. });
  845. }
  846. /// <summary>
  847. /// Deletes the attachment with the specified key asynchronously
  848. /// </summary>
  849. /// <param name="key">The key.</param>
  850. /// <param name="etag">The etag.</param>
  851. public Task DeleteAttachmentAsync(string key, Guid? etag)
  852. {
  853. var metadata = new RavenJObject();
  854. if (etag != null)
  855. metadata["ETag"] = new RavenJValue(etag.Value.ToString());
  856. var request = jsonRequestFactory.CreateHttpJsonRequest(this, url.Static(key), "DELETE", metadata, credentials, convention);
  857. request.AddOperationHeaders(OperationsHeaders);
  858. return request.ExecuteRequest();
  859. }
  860. /// <summary>
  861. /// Ensures that the silverlight startup tasks have run
  862. /// </summary>
  863. public Task EnsureSilverlightStartUpAsync()
  864. {
  865. return url
  866. .SilverlightEnsuresStartup()
  867. .NoCache()
  868. .ToJsonRequest(this, credentials, convention)
  869. .ReadResponseBytesAsync();
  870. }
  871. ///<summary>
  872. /// Get the possible terms for the specified field in the index asynchronously
  873. /// You can page through the results by use fromValue parameter as the
  874. /// starting point for the next query
  875. ///</summary>
  876. ///<returns></returns>
  877. public Task<string[]> GetTermsAsync(string index, string field, string fromValue, int pageSize)
  878. {
  879. return url.Terms(index, field, fromValue, pageSize)
  880. .NoCache()
  881. .ToJsonRequest(this, credentials, convention)
  882. .ReadResponseJsonAsync()
  883. .ContinueWith(task =>
  884. {
  885. var json = ((RavenJArray)task.Result);
  886. return json.Select(x => x.Value<string>()).ToArray();
  887. });
  888. }
  889. /// <summary>
  890. /// Disable all caching within the given scope
  891. /// </summary>
  892. public IDisposable DisableAllCaching()
  893. {
  894. return null; // we don't implement this
  895. }
  896. /// <summary>
  897. /// The profiling information
  898. /// </summary>
  899. public ProfilingInformation ProfilingInformation
  900. {
  901. get { return profilingInformation; }
  902. }
  903. }
  904. }
  905. #endif