Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections;
11 : using System.Collections.Generic;
12 : using System.IO;
13 : using System.Linq;
14 : using System.Linq.Expressions;
15 : using System.Text;
16 : using cdmdotnet.Logging;
17 : using Cqrs.Entities;
18 : using Cqrs.Events;
19 : using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
20 : using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling;
21 : using Microsoft.Practices.TransientFaultHandling;
22 : using Microsoft.WindowsAzure.Storage;
23 : using Newtonsoft.Json;
24 :
25 : namespace Cqrs.Azure.BlobStorage
26 : {
27 : /// <summary>
28 : /// A <see cref="IEnumerable{TData}"/> that uses Azure Storage for storage.
29 : /// </summary>
30 : public abstract class StorageStore<TData, TSource>
31 : : IEnumerable<TData>
32 1 : {
33 : /// <summary>
34 : /// Gets the collection of writeable <see cref="CloudStorageAccount"/>.
35 : /// </summary>
36 : protected IList<Tuple<CloudStorageAccount, TSource>> WritableCollection { get; private set; }
37 :
38 : /// <summary>
39 : /// Gets the readable <see cref="CloudStorageAccount"/>.
40 : /// </summary>
41 : protected CloudStorageAccount ReadableStorageAccount { get; private set; }
42 :
43 : /// <summary>
44 : /// Gets the readable <typeparamref name="TSource"/>.
45 : /// </summary>
46 : internal TSource ReadableSource { get; private set; }
47 :
48 : /// <summary>
49 : /// Gets the <see cref="ILogger"/>.
50 : /// </summary>
51 : protected ILogger Logger { get; private set; }
52 :
53 : /// <summary>
54 : /// Gets or sets the <see cref="Func{Tstring}"/> that returns the name of the container.
55 : /// </summary>
56 : protected Func<string> GetContainerName { get; set; }
57 :
58 : /// <summary>
59 : /// Gets or sets the <see cref="Func{Tstring}"/> that returns if the container is public or not.
60 : /// </summary>
61 : protected Func<bool> IsContainerPublic { get; set; }
62 :
63 : /// <summary>
64 : /// Initializes a new instance of the <see cref="StorageStore{TData,TSource}"/> class using the specified container.
65 : /// </summary>
66 1 : protected StorageStore(ILogger logger)
67 : {
68 : Logger = logger;
69 : }
70 :
71 : /// <summary>
72 : /// The default <see cref="JsonSerializerSettings"/> to use.
73 : /// </summary>
74 : public static JsonSerializerSettings DefaultSettings { get; private set; }
75 :
76 : static StorageStore()
77 : {
78 : DefaultSettings = DefaultJsonSerializerSettings.DefaultSettings;
79 : }
80 :
81 : /// <summary>
82 : /// Initialises the <see cref="StorageStore{TData,TSource}"/>.
83 : /// </summary>
84 1 : protected virtual void Initialise(IStorageStoreConnectionStringFactory storageDataStoreConnectionStringFactory)
85 : {
86 : WritableCollection = new List<Tuple<CloudStorageAccount, TSource>>();
87 : ReadableStorageAccount = CloudStorageAccount.Parse(storageDataStoreConnectionStringFactory.GetReadableConnectionString());
88 : ReadableSource = CreateSource(ReadableStorageAccount, GetContainerName(), IsContainerPublic());
89 :
90 : foreach (string writableConnectionString in storageDataStoreConnectionStringFactory.GetWritableConnectionStrings())
91 : {
92 : CloudStorageAccount storageAccount = CloudStorageAccount.Parse(writableConnectionString);
93 : TSource container = CreateSource(storageAccount, GetContainerName(), IsContainerPublic());
94 :
95 : WritableCollection.Add(new Tuple<CloudStorageAccount, TSource>(storageAccount, container));
96 : }
97 : }
98 :
99 : #region Implementation of IEnumerable
100 :
101 : /// <summary>
102 : /// Returns an enumerator that iterates through the collection.
103 : /// </summary>
104 : /// <returns>
105 : /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
106 : /// </returns>
107 1 : public abstract IEnumerator<TData> GetEnumerator();
108 :
109 : /// <summary>
110 : /// Returns an enumerator that iterates through a collection.
111 : /// </summary>
112 : /// <returns>
113 : /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
114 : /// </returns>
115 : IEnumerator IEnumerable.GetEnumerator()
116 : {
117 : return GetEnumerator();
118 : }
119 :
120 : #endregion
121 :
122 : #region Implementation of IQueryable
123 :
124 : /// <summary>
125 : /// Gets the expression tree that is associated with the instance of <see cref="T:System.Linq.IQueryable"/>.
126 : /// </summary>
127 : /// <returns>
128 : /// The <see cref="T:System.Linq.Expressions.Expression"/> that is associated with this instance of <see cref="T:System.Linq.IQueryable"/>.
129 : /// </returns>
130 : public abstract Expression Expression { get; }
131 :
132 : /// <summary>
133 : /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of <see cref="T:System.Linq.IQueryable"/> is executed.
134 : /// </summary>
135 : /// <returns>
136 : /// A <see cref="T:System.Type"/> that represents the type of the element(s) that are returned when the expression tree associated with this object is executed.
137 : /// </returns>
138 : public abstract Type ElementType { get; }
139 :
140 : /// <summary>
141 : /// Gets the query provider that is associated with this data source.
142 : /// </summary>
143 : /// <returns>
144 : /// The <see cref="T:System.Linq.IQueryProvider"/> that is associated with this data source.
145 : /// </returns>
146 : public abstract IQueryProvider Provider { get; }
147 :
148 : #endregion
149 :
150 : #region Implementation of IDisposable
151 :
152 : /// <summary>
153 : /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
154 : /// </summary>
155 1 : public void Dispose()
156 : {
157 : ReadableSource = default(TSource);
158 : ReadableStorageAccount = null;
159 :
160 : WritableCollection = null;
161 : }
162 :
163 : #endregion
164 :
165 : #region Implementation of IDataStore<TData>
166 :
167 : /// <summary>
168 : /// Add the provided <paramref name="data"/> to the data store and persist the change.
169 : /// </summary>
170 1 : public abstract void Add(TData data);
171 :
172 : /// <summary>
173 : /// Add the provided <paramref name="data"/> to the data store and persist the change.
174 : /// </summary>
175 1 : public virtual void Add(IEnumerable<TData> data)
176 : {
177 : foreach (TData dataItem in data)
178 : Add(dataItem);
179 : }
180 :
181 : /// <summary>
182 : /// Remove the provided <paramref name="data"/> (normally by <see cref="IEntity.Rsn"/>) from the data store and persist the change.
183 : /// </summary>
184 1 : public abstract void Destroy(TData data);
185 :
186 : /// <summary>
187 : /// Remove all contents (normally by use of a truncate operation) from the data store and persist the change.
188 : /// </summary>
189 1 : public abstract void RemoveAll();
190 :
191 : /// <summary>
192 : /// Update the provided <paramref name="data"/> in the data store and persist the change.
193 : /// </summary>
194 1 : public abstract void Update(TData data);
195 :
196 : #endregion
197 :
198 : /// <summary>
199 : /// Creates a <typeparamref name="TSource" /> with the specified name <paramref name="sourceName"/> if it doesn't already exist.
200 : /// </summary>
201 : /// <param name="storageAccount">The storage account to create the container is</param>
202 : /// <param name="sourceName">The name of the source.</param>
203 : /// <param name="isPublic">Whether or not this source is publicly accessible.</param>
204 1 : protected abstract TSource CreateSource(CloudStorageAccount storageAccount, string sourceName, bool isPublic = true);
205 :
206 : /// <summary>
207 : /// Gets the provided <paramref name="sourceName"/> in a safe to use in lower case format.
208 : /// </summary>
209 : /// <param name="sourceName">The name to make safe.</param>
210 1 : protected virtual string GetSafeSourceName(string sourceName)
211 : {
212 : return GetSafeSourceName(sourceName, true);
213 : }
214 :
215 : /// <summary>
216 : /// Gets the provided <paramref name="sourceName"/> in a safe to use in format.
217 : /// </summary>
218 : /// <param name="sourceName">The name to make safe.</param>
219 : /// <param name="lowerCaseName">Indicates if the generated name is forced into a lower case format.</param>
220 1 : protected virtual string GetSafeSourceName(string sourceName, bool lowerCaseName)
221 : {
222 : if (sourceName.Contains(":"))
223 : return sourceName;
224 :
225 : string safeContainerName = sourceName.Replace(@"\", @"/").Replace(@"/", @"-");
226 : if (lowerCaseName)
227 : safeContainerName = safeContainerName.ToLowerInvariant();
228 : if (safeContainerName.StartsWith("-"))
229 : safeContainerName = safeContainerName.Substring(1);
230 : if (safeContainerName.EndsWith("-"))
231 : safeContainerName = safeContainerName.Substring(0, safeContainerName.Length - 1);
232 : safeContainerName = safeContainerName.Replace(" ", "-");
233 :
234 : return safeContainerName;
235 : }
236 :
237 : /// <summary>
238 : /// Characters Disallowed in Key Fields
239 : ///
240 : /// The following characters are not allowed in values for the PartitionKey and RowKey properties:
241 : ///
242 : /// The forward slash (/) character
243 : /// The backslash (\) character
244 : /// The number sign (#) character
245 : /// The question mark (?) character
246 : /// Control characters from U+0000 to U+001F, including:
247 : /// The horizontal tab (\t) character
248 : /// The linefeed (\n) character
249 : /// The carriage return (\r) character
250 : /// Control characters from U+007F to U+009F
251 : /// </summary>
252 : /// <param name="sourceName"></param>
253 : /// <returns></returns>
254 : internal static string GetSafeStorageKey(string sourceName)
255 : {
256 : var sb = new StringBuilder();
257 : foreach (var c in sourceName
258 : .Where(c => c != '/'
259 : && c != '\\'
260 : && c != '#'
261 : && c != '/'
262 : && c != '?'
263 : && !char.IsControl(c)))
264 : sb.Append(c);
265 : return sb.ToString();
266 : }
267 :
268 : /// <summary>
269 : /// Gets the default retry policy dedicated to handling transient conditions with Windows Azure Storage.
270 : /// </summary>
271 : protected virtual RetryPolicy AzureStorageRetryPolicy
272 : {
273 : get
274 : {
275 : RetryManager retryManager = EnterpriseLibraryContainer.Current.GetInstance<RetryManager>();
276 : RetryPolicy retryPolicy = retryManager.GetDefaultAzureStorageRetryPolicy();
277 : retryPolicy.Retrying += (sender, args) =>
278 : {
279 : var msg = string.Format("Retrying action - Count: {0}, Delay: {1}", args.CurrentRetryCount, args.Delay);
280 : Logger.LogWarning(msg, exception: args.LastException);
281 : };
282 : return retryPolicy;
283 : }
284 : }
285 :
286 : /// <summary>
287 : /// Deserialise the provided <paramref name="dataStream"/> from its <see cref="Stream"/> representation.
288 : /// </summary>
289 : /// <param name="dataStream">A <see cref="Stream"/> representation of an <typeparamref name="TData"/> to deserialise.</param>
290 1 : protected virtual TData Deserialise(Stream dataStream)
291 : {
292 : using (dataStream)
293 : {
294 : using (var reader = new StreamReader(dataStream))
295 : {
296 : string jsonContents = reader.ReadToEnd();
297 : TData obj = Deserialise(jsonContents);
298 : return obj;
299 : }
300 : }
301 : }
302 :
303 : /// <summary>
304 : /// Deserialise the provided <paramref name="json"/> from its <see cref="string"/> representation.
305 : /// </summary>
306 : /// <param name="json">A <see cref="string"/> representation of an <typeparamref name="TData"/> to deserialise.</param>
307 1 : protected virtual TData Deserialise(string json)
308 : {
309 : using (var stringReader = new StringReader(json))
310 : using (var jsonTextReader = new JsonTextReader(stringReader))
311 : return GetSerialiser().Deserialize<TData>(jsonTextReader);
312 : }
313 :
314 : /// <summary>
315 : /// Serialise the provided <paramref name="data"/>.
316 : /// </summary>
317 : /// <param name="data">The <typeparamref name="TData"/> being serialised.</param>
318 : /// <returns>A <see cref="Stream"/> representation of the provided <paramref name="data"/>.</returns>
319 1 : protected virtual Stream Serialise(TData data)
320 : {
321 : string dataContent = JsonConvert.SerializeObject(data, GetSerialisationSettings());
322 :
323 : byte[] byteArray = Encoding.UTF8.GetBytes(dataContent);
324 : var stream = new MemoryStream(byteArray);
325 :
326 : return stream;
327 : }
328 :
329 : /// <summary>
330 : /// Returns <see cref="DefaultSettings"/>
331 : /// </summary>
332 : /// <returns><see cref="DefaultSettings"/></returns>
333 1 : protected virtual JsonSerializerSettings GetSerialisationSettings()
334 : {
335 : return DefaultSettings;
336 : }
337 :
338 : /// <summary>
339 : /// Creates a new <see cref="JsonSerializer"/> using the settings from <see cref="GetSerialisationSettings"/>.
340 : /// </summary>
341 : /// <returns>A new instance of <see cref="JsonSerializer"/>.</returns>
342 1 : protected virtual JsonSerializer GetSerialiser()
343 : {
344 : JsonSerializerSettings settings = GetSerialisationSettings();
345 : return JsonSerializer.Create(settings);
346 : }
347 : }
348 : }
|