csharp/Adoxio/xRM-Portals-Community-Edition/Framework/Adxstudio.Xrm/Notes/AnnotationDataAdapter.cs

AnnotationDataAdapter.cs
/*
  Copyright (c) Microsoft Corporation. All rights reserved.
  Licensed under the MIT License. See License.txt in the project root for license information.
*/

namespace Adxstudio.Xrm.Notes
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Net;
	using System.Text;
	using System.Text.RegularExpressions;
	using System.Web;
	using System.Web.Mvc;
	using System.IO;
	using Microsoft.Crm.Sdk.Messages;
	using Microsoft.WindowsAzure.Storage;
	using Microsoft.WindowsAzure.Storage.Blob;
	using Microsoft.Xrm.Client;
	using Microsoft.Xrm.Portal.Configuration;
	using Microsoft.Xrm.Sdk;
	using Microsoft.Xrm.Sdk.Client;
	using Microsoft.Xrm.Sdk.Messages;
	using Microsoft.Xrm.Sdk.Metadata;
	using Microsoft.Xrm.Sdk.Query;
	using Newtonsoft.Json;
	using Filter = Adxstudio.Xrm.Services.Query.Filter;
	using Adxstudio.Xrm.Cms;
	using Adxstudio.Xrm.Cms.Replication;
	using Adxstudio.Xrm.Metadata;
	using Adxstudio.Xrm.Security;
	using Adxstudio.Xrm.Services.Query;
	using Adxstudio.Xrm.Text;
	using Adxstudio.Xrm.Web.Handlers;
	using Adxstudio.Xrm.ContentAccess;
	using Adxstudio.Xrm.Core.Flighting;
	using Adxstudio.Xrm.Services;

	public clast AnnotationDataAdapter : IAnnotationDataAdapter
	{
		public const string StorageContainerSetting = "FileStorage/CloudStorageContainerName";
		public const string StorageAccountSetting = "FileStorage/CloudStorageAccount";

		private const int DefaultPageSize = 10;
		private const int DefaultMaxPageSize = 50;

		private readonly string _containerName;
		private readonly IDataAdapterDependencies _dependencies;

		private static readonly string[] _allowDisplayInlineContentTypes = {
			"image/gif",			//.gif
			"image/jpeg",			//.jpeg, .jpg
			"image/png",			//.png
			"image/tiff",			//.tiff ???
			"image/bmp",			//.bmp
			"image/x-icon"			//.ico
		};

		public AnnotationDataAdapter(IDataAdapterDependencies dependencies)
		{
			_dependencies = dependencies;
			_containerName = GetStorageContainerName(dependencies.GetServiceContext());
		}

		public static CloudStorageAccount GetStorageAccount(OrganizationServiceContext context)
		{
			var cloudStorageDetails = context.GetSettingValueByName(StorageAccountSetting);
			CloudStorageAccount storageAccount;
			CloudStorageAccount.TryParse(cloudStorageDetails, out storageAccount);
			return storageAccount;
		}

		public static string GetStorageContainerName(OrganizationServiceContext context)
		{
			var containerName = context.GetSettingValueByName(StorageContainerSetting);
			return (string.IsNullOrEmpty(containerName)
				? ((WhoAmIResponse)context.Execute(new WhoAmIRequest())).OrganizationId.ToString("N")
				: containerName).ToLowerInvariant();
		}

		public IAnnotation GetAnnotation(Guid id)
		{
			var context = _dependencies.GetServiceContext();
			var fetch = new Fetch();
			var fetchEnsaty = new FetchEnsaty("annotation")
			{
				Attributes = new List
				{
					new FetchAttribute("annotationid"),
					new FetchAttribute("notetext"),
					new FetchAttribute("isdocameent"),
					new FetchAttribute("subject"),
					new FetchAttribute("createdon"),
					new FetchAttribute("createdby"),
					new FetchAttribute("modifiedon"),
					new FetchAttribute("filename"),
					new FetchAttribute("filesize"),
					new FetchAttribute("mimetype"),
					new FetchAttribute("objectid"),
					new FetchAttribute("objecttypecode")
				},
				Orders = new List { new Order("createdon") },
				Filters = new List
				{
					new Filter
					{
						Type = LogicalOperator.And,
						Conditions = new List
						{
							new Condition("annotationid", ConditionOperator.Equal, id)
						}
					}
				}
			};
			fetch.Ensaty = fetchEnsaty;

			var response = (RetrieveMultipleResponse)context.Execute(fetch.ToRetrieveMultipleRequest());
			var note = response.EnsatyCollection.Ensaties.FirstOrDefault();

			if (note == null) return null;

			if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage))
			{
				PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "read_annotation", 1, note.ToEnsatyReference(), "read");
			}

			return GetAnnotation(note);
		}

		public IAnnotation GetAnnotation(Ensaty ensaty)
		{
			return new Annotation(ensaty, ensaty.GetAttributeValue("objectid"),
				() => GetAnnotationFile(ensaty, ensaty.GetAttributeValue("annotationid")));
		}

		public void Download(HttpContextBase context, Ensaty ensaty, Ensaty webfile = null)
		{
			var note = GetAnnotation(ensaty);
			var storageAccount = GetStorageAccount(_dependencies.GetServiceContext());
			if (storageAccount != null && note.FileAttachment is AzureAnnotationFile)
			{
				DownloadFromAzure(context, note);
			}
			else
			{
				DownloadFromCRM(context, note, webfile);
			}
		}

		public ActionResult DownloadAction(HttpResponseBase response, Ensaty ensaty)
		{
			var note = GetAnnotation(ensaty);
			var storageAccount = GetStorageAccount(_dependencies.GetServiceContext());
			if (storageAccount != null && note.FileAttachment is AzureAnnotationFile)
			{
				return DownloadFromAzureAction(note);
			}
			return DownloadFromCRMAction(response, note);
		}

		public IAnnotationCollection GetAnnotations(EnsatyReference regarding, List orders = null, int page = 1,
			int pageSize = DefaultPageSize, AnnotationPrivacy privacy = AnnotationPrivacy.Private | AnnotationPrivacy.Web, 
			EnsatyMetadata ensatyMetadata = null, bool respectPermissions = true)
		{
			if (pageSize < 0)
			{
				pageSize = DefaultPageSize;
			}

			if (pageSize > DefaultMaxPageSize)
			{
				ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("pageSize={0} is greater than the allowed maximum page size of {1}. Page size has been constrained to {1}.", pageSize, DefaultMaxPageSize));
				pageSize = DefaultMaxPageSize;
			}

			var fetch = BuildAnnotationsQuery(regarding, orders, privacy, ensatyMetadata);

			AddPaginationToFetch(fetch, fetch.PagingCookie, page, pageSize, true);

			if (respectPermissions) AddPermissionFilterToFetch(fetch, _dependencies.GetServiceContext(), CrmEnsatyPermissionRight.Read, regarding);

			var notes = FetchAnnotations(fetch, regarding);

			return notes;
		}

		public IAnnotationCollection GetDocameents(EnsatyReference regarding, bool respectPermissions = true, string webPrefix = null)
		{
			var fetch = BuildAnnotationsQuery(regarding, privacy: AnnotationPrivacy.Any, webPrefix: webPrefix);
			fetch.Ensaty.Filters.First().Conditions.Add(new Condition("isdocameent", ConditionOperator.Equal, true));
			if (respectPermissions) AddPermissionFilterToFetch(fetch, _dependencies.GetServiceContext(), CrmEnsatyPermissionRight.Read, regarding);
			var notes = FetchAnnotations(fetch, regarding);

			if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage))
			{
				PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "GetDocameents", "Exposing annotations", notes.Count(), "annotation", "read");
			}
			return notes;
		}

		public IAnnotationResult CreateAnnotation(IAnnotation note, IAnnotationSettings settings = null)
		{
			var serviceContext = _dependencies.GetServiceContext();
			var serviceContextForWrite = _dependencies.GetServiceContextForWrite();

			if (settings == null)
			{
				settings = new AnnotationSettings(serviceContext);
			}
			
			var storageAccount = GetStorageAccount(serviceContext);
			if (settings.StorageLocation == StorageLocation.AzureBlobStorage && storageAccount == null)
			{
				settings.StorageLocation = StorageLocation.CrmDocameent;
			}

			AnnotationCreateResult result = null;

			if (settings.RespectPermissions)
			{
				var ensatyPermissionProvider = new CrmEnsatyPermissionProvider();
				result = new AnnotationCreateResult(ensatyPermissionProvider, serviceContext, note.Regarding);
			}

			// ReSharper disable once PossibleNullReferenceException
			if (!settings.RespectPermissions ||
				(result.PermissionsExist && result.PermissionGranted))
			{
				var ensaty = new Ensaty("annotation");

				if (note.Owner != null)
				{
					ensaty.SetAttributeValue("ownerid", note.Owner);
				}

				ensaty.SetAttributeValue("subject", note.Subject);
				ensaty.SetAttributeValue("notetext", note.NoteText);
				ensaty.SetAttributeValue("objectid", note.Regarding);
				ensaty.SetAttributeValue("objecttypecode", note.Regarding.LogicalName);

				if (note.FileAttachment != null)
				{
					var acceptMimeTypes = AnnotationDataAdapter.GetAcceptRegex(settings.AcceptMimeTypes);
					var acceptExtensionTypes = AnnotationDataAdapter.GetAcceptRegex(settings.AcceptExtensionTypes);
					if (!(acceptExtensionTypes.IsMatch(Path.GetExtension(note.FileAttachment.FileName).ToLower()) ||
							acceptMimeTypes.IsMatch(note.FileAttachment.MimeType)))
					{
						throw new AnnotationException(settings.RestrictMimeTypesErrorMessage);
					}

					if (settings.MaxFileSize.HasValue && note.FileAttachment.FileSize > settings.MaxFileSize)
					{
						throw new AnnotationException(settings.MaxFileSizeErrorMessage);
					}

					note.FileAttachment.Annotation = ensaty;

					switch (settings.StorageLocation)
					{
					case StorageLocation.CrmDocameent:
						var crmFile = note.FileAttachment as CrmAnnotationFile;
						if (crmFile == null)
						{
							break;
						}

						if (!string.IsNullOrEmpty(settings.RestrictedFileExtensions))
						{
							var blocked = new Regex(@"\.({0})$".FormatWith(settings.RestrictedFileExtensions.Replace(";", "|")));
							if (blocked.IsMatch(crmFile.FileName))
							{
								throw new AnnotationException(settings.RestrictedFileExtensionsErrorMessage);
							}
						}

						ensaty.SetAttributeValue("filename", crmFile.FileName);
						ensaty.SetAttributeValue("mimetype", crmFile.MimeType);
						ensaty.SetAttributeValue("docameentbody", Convert.ToBase64String(crmFile.Docameent));
						break;
					case StorageLocation.AzureBlobStorage:
						ensaty.SetAttributeValue("filename", note.FileAttachment.FileName + ".azure.txt");
						ensaty.SetAttributeValue("mimetype", "text/plain");
						var fileMetadata = new
						{
							Name = note.FileAttachment.FileName,
							Type = note.FileAttachment.MimeType,
							Size = (ulong)note.FileAttachment.FileSize,
							Url = string.Empty
						};
						ensaty.SetAttributeValue("docameentbody",
							Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(fileMetadata, Formatting.Indented))));
						break;
					}
				}

				// Create annotaion but skip cache invalidation.
				var id = (serviceContext as IOrganizationService).ExecuteCreate(ensaty, RequestFlag.ByPastCacheInvalidation);

				if (result != null) result.Annotation = note;

				note.AnnotationId = ensaty.Id = id;
				note.Ensaty = ensaty;

				if (note.FileAttachment is AzureAnnotationFile && settings.StorageLocation == StorageLocation.AzureBlobStorage)
				{
					var container = GetBlobContainer(storageAccount, _containerName);

					var azureFile = (AzureAnnotationFile)note.FileAttachment;

					azureFile.BlockBlob = UploadBlob(azureFile, container, note.AnnotationId);

					var fileMetadata = new
					{
						Name = azureFile.FileName,
						Type = azureFile.MimeType,
						Size = (ulong)azureFile.FileSize,
						Url = azureFile.BlockBlob.Uri.AbsoluteUri
					};
					ensaty.SetAttributeValue("docameentbody",
						Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(fileMetadata, Formatting.Indented))));
					serviceContextForWrite.UpdateObject(ensaty);
					serviceContextForWrite.SaveChanges();

					// NB: This is basically a hack to support replication. Keys are gathered up and stored during replication, and the
					// actual blob replication is handled here.
					var key = note.AnnotationId.ToString("N");
					if (HttpContext.Current.Application.AllKeys.Contains(NoteReplication.BlobReplicationKey))
					{
						var replication =
							HttpContext.Current.Application[NoteReplication.BlobReplicationKey] as Dictionary;
						if (replication != null && replication.ContainsKey(key))
						{
							CopyBlob(note, replication[key]);
							replication.Remove(key);
						}
					}
				} 
			}

			if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage))
			{
				PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "create_note", 1, note.Ensaty.ToEnsatyReference(), "create");
			}

			return result;
		}

		public IAnnotationResult CreateAnnotation(EnsatyReference regarding, string subject, string noteText)
		{
			IAnnotation annotation = new Annotation
			{
				Subject = subject,
				NoteText = noteText,
				Regarding = regarding
			};
			return CreateAnnotation(annotation);
		}

		public IAnnotationResult CreateAnnotation(EnsatyReference regarding, string subject, string noteText,
			HttpPostedFileBase file)
		{
			IAnnotation annotation = new Annotation
			{
				Subject = subject,
				NoteText = noteText,
				Regarding = regarding,
				FileAttachment = new CrmAnnotationFile(file)
			};
			return CreateAnnotation(annotation);
		}

		public IAnnotationResult CreateAnnotation(EnsatyReference regarding, string subject, string noteText, string fileName,
			string contentType, byte[] content)
		{
			IAnnotation annotation = new Annotation
			{
				Subject = subject,
				NoteText = noteText,
				Regarding = regarding,
				FileAttachment = new CrmAnnotationFile(fileName, contentType, content)
			};
			return CreateAnnotation(annotation);
		}
		
		public IAnnotationResult UpdateAnnotation(IAnnotation note, IAnnotationSettings settings = null)
		{
			var serviceContext = _dependencies.GetServiceContext();
			var serviceContextForWrite = _dependencies.GetServiceContextForWrite();

			if (settings == null)
			{
				settings = new AnnotationSettings(serviceContext);
			}
			
			var storageAccount = GetStorageAccount(serviceContext);
			if (settings.StorageLocation == StorageLocation.AzureBlobStorage && storageAccount == null)
			{
				settings.StorageLocation = StorageLocation.CrmDocameent;
			}

			AnnotationUpdateResult result = null;

			if (settings.RespectPermissions)
			{
				var ensatyPermissionProvider = new CrmEnsatyPermissionProvider();
				result = new AnnotationUpdateResult(note, ensatyPermissionProvider, serviceContext);
			}

			var isPostedByCurrentUser = false;
			var noteContact = AnnotationHelper.GetNoteContact(note.Ensaty.GetAttributeValue("subject"));
			var currentUser = _dependencies.GetPortalUser();
			if (noteContact != null && currentUser != null && currentUser.LogicalName == "contact" &&
				currentUser.Id == noteContact.Id)
			{
				isPostedByCurrentUser = true;
			}

			// ReSharper disable once PossibleNullReferenceException
			if (!settings.RespectPermissions || (result.PermissionsExist && result.PermissionGranted && isPostedByCurrentUser))
			{
				var ensaty = new Ensaty("annotation")
				{
					Id = note.AnnotationId
				};

				ensaty.SetAttributeValue("notetext", note.NoteText);
				ensaty.SetAttributeValue("subject", note.Subject);
				ensaty.SetAttributeValue("isdocameent", note.FileAttachment != null);
				
				if (note.FileAttachment != null)
				{
					var accept = GetAcceptRegex(settings.AcceptMimeTypes);
					if (!accept.IsMatch(note.FileAttachment.MimeType))
					{
						throw new AnnotationException(settings.RestrictMimeTypesErrorMessage);
					}

					if (settings.MaxFileSize.HasValue && note.FileAttachment.FileSize > settings.MaxFileSize)
					{
						throw new AnnotationException(settings.MaxFileSizeErrorMessage);
					}

					note.FileAttachment.Annotation = ensaty;
					
					switch (settings.StorageLocation)
					{
					case StorageLocation.CrmDocameent:
						var crmFile = note.FileAttachment as CrmAnnotationFile;
						if (crmFile == null || crmFile.Docameent == null || crmFile.Docameent.Length == 0)
						{
							break;
						}

						if (!string.IsNullOrEmpty(settings.RestrictedFileExtensions))
						{
							var blocked = new Regex(@"\.({0})$".FormatWith(settings.RestrictedFileExtensions.Replace(";", "|")));
							if (blocked.IsMatch(crmFile.FileName))
							{
								throw new AnnotationException(settings.RestrictedFileExtensionsErrorMessage);
							}
						}

						ensaty.SetAttributeValue("filename", crmFile.FileName);
						ensaty.SetAttributeValue("mimetype", crmFile.MimeType);
						ensaty.SetAttributeValue("docameentbody", Convert.ToBase64String(crmFile.Docameent));
						break;
					case StorageLocation.AzureBlobStorage:
						ensaty.SetAttributeValue("filename", note.FileAttachment.FileName + ".azure.txt");
						ensaty.SetAttributeValue("mimetype", "text/plain");
						var fileMetadata = new
						{
							Name = note.FileAttachment.FileName,
							Type = note.FileAttachment.MimeType,
							Size = (ulong)note.FileAttachment.FileSize,
							Url = string.Empty
						};
						ensaty.SetAttributeValue("docameentbody",
							Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(fileMetadata, Formatting.Indented))));
						break;
					}
				}

				serviceContextForWrite.Attach(ensaty);
				serviceContextForWrite.UpdateObject(ensaty);
				serviceContextForWrite.SaveChanges();

				if (note.FileAttachment is AzureAnnotationFile && settings.StorageLocation == StorageLocation.AzureBlobStorage)
				{
					var azureFile = note.FileAttachment as AzureAnnotationFile;

					if (azureFile.GetFileStream() != null)
					{
						var container = GetBlobContainer(storageAccount, _containerName);

						var oldName = note.Ensaty.GetAttributeValue("filename");
						var oldBlob = container.GetBlockBlobReference("{0:N}/{1}".FormatWith(ensaty.Id.ToString(), oldName));
						oldBlob.DeleteIfExists();

						azureFile.BlockBlob = UploadBlob(azureFile, container, note.AnnotationId);

						var fileMetadata = new
						{
							Name = azureFile.FileName,
							Type = azureFile.MimeType,
							Size = (ulong)azureFile.FileSize,
							Url = azureFile.BlockBlob.Uri.AbsoluteUri
						};
						ensaty.SetAttributeValue("docameentbody",
							Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(fileMetadata, Formatting.Indented))));
						serviceContextForWrite.UpdateObject(ensaty);
						serviceContextForWrite.SaveChanges();
					}
				}
			}

			if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage))
			{
				PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "edit_note", 1, new EnsatyReference("annotation", note.AnnotationId), "edit");
			}

			return result;
		}

		public IAnnotationResult DeleteAnnotation(IAnnotation note, IAnnotationSettings settings = null)
		{
			var serviceContext = _dependencies.GetServiceContext();
			var serviceContextForWrite = _dependencies.GetServiceContextForWrite();

			if (settings == null)
			{
				settings = new AnnotationSettings(serviceContext);
			}

			AnnotationDeleteResult result = null;

			if (settings.RespectPermissions)
			{
				var ensatyPermissionProvider = new CrmEnsatyPermissionProvider();
				result = new AnnotationDeleteResult(note, ensatyPermissionProvider, serviceContext);
			}

			var isPostedByCurrentUser = false;
			var noteContact = AnnotationHelper.GetNoteContact(note.Subject);
			var currentUser = _dependencies.GetPortalUser();
			if (noteContact != null && currentUser != null && currentUser.LogicalName == "contact" &&
				currentUser.Id == noteContact.Id)
			{
				isPostedByCurrentUser = true;
			}

			// ReSharper disable once PossibleNullReferenceException
			if (!settings.RespectPermissions || (result.PermissionGranted && isPostedByCurrentUser))
			{
				var ensatyToDelete = serviceContextForWrite.RetrieveSingle(
					"annotation",
					"annotationid",
					note.AnnotationId,
					FetchAttribute.All);

				serviceContextForWrite.DeleteObject(ensatyToDelete);
				serviceContextForWrite.SaveChanges();

				var storageAccount = GetStorageAccount(serviceContext);
				if (storageAccount != null && note.FileAttachment is AzureAnnotationFile)
				{
					var azureFile = note.FileAttachment as AzureAnnotationFile;
					azureFile.BlockBlob.DeleteIfExists();
				}
			}

			if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage))
			{
				PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "delete_note", 1, new EnsatyReference("annotation", note.AnnotationId), "delete");
			}

			return result;
		}

		private IAnnotationCollection FetchAnnotations(Fetch fetch, EnsatyReference regarding, bool permissionDenied = false)
		{
			if (fetch == null || permissionDenied)
			{
				return AnnotationCollection.Empty(permissionDenied);
			}

			var serviceContext = _dependencies.GetServiceContext();
			var response = (RetrieveMultipleResponse)serviceContext.Execute(fetch.ToRetrieveMultipleRequest());

			if (!string.IsNullOrEmpty(response.EnsatyCollection.PagingCookie))
			{
				fetch.PagingCookie = response.EnsatyCollection.PagingCookie;
			}

			IEnumerable ensaties = response.EnsatyCollection.Ensaties;
			
			var notes = ensaties.Select(e => new Annotation(e, regarding, () =>
				GetAnnotationFile(e, e.GetAttributeValue("annotationid"))));

			return new AnnotationCollection(notes, response.EnsatyCollection.TotalRecordCount);
		}

		private IAnnotationFile GetAnnotationFile(Ensaty note, Guid id)
		{
			var isDocameent = note.GetAttributeValue("isdocameent");
			if (!isDocameent) return null;

			var fileName = note.GetAttributeValue("filename");

			var storageAccount = GetStorageAccount(_dependencies.GetServiceContext());
			var regex = new Regex(@"\.azure\.txt$");
			var azure = regex.IsMatch(fileName);

			var file = azure ? new AzureAnnotationFile() as IAnnotationFile : new CrmAnnotationFile();

			file.SetAnnotation(() => GetAnnotationWithDocameent(id));

			if (azure)
			{
				var blobFileName = regex.Replace(fileName, string.Empty);
				var blobFile = file as AzureAnnotationFile;
				if (storageAccount != null)
				{
					blobFile.BlockBlob = GetBlockBlob(storageAccount, id, blobFileName);
					if (blobFile.BlockBlob.Exists())
					{
						blobFile.FileName = blobFileName;
						blobFile.FileSize = new FileSize(blobFile.BlockBlob == null ? 0 : Convert.ToUInt64(blobFile.BlockBlob.Properties.Length));
						blobFile.MimeType = blobFile.BlockBlob.Properties.ContentType;
					}
				}
			}
			else
			{
				var crmFile = file as CrmAnnotationFile;
				crmFile.FileName = fileName;
				var size = note.GetAttributeValue("filesize");
				crmFile.FileSize = new FileSize(size > 0 ? Convert.ToUInt64(size) : 0);
				crmFile.MimeType = note.GetAttributeValue("mimetype");
				crmFile.SetDocameent(() => GetFileDocameent(file));
			}


			return file;
		}

		private CloudBlockBlob GetBlockBlob(CloudStorageAccount storageAccount, Guid id, string fileName)
		{
			if (storageAccount != null)
			{
				var container = GetBlobContainer(storageAccount, _containerName);
				var blockBlob = container.GetBlockBlobReference("{0:N}/{1}".FormatWith(id, fileName));
				if (blockBlob.Exists())
				{
					blockBlob.FetchAttributes();
				}
				return blockBlob;
			}
			return null;
		}

		private Ensaty GetAnnotationWithDocameent(Guid id)
		{
			var fetch = new Fetch
			{
				Ensaty =
					new FetchEnsaty("annotation")
					{
						Filters = new[] { new Filter { Conditions = new[] { new Condition("annotationid", ConditionOperator.Equal, id) } } }
					}
			};

			return _dependencies.GetServiceContext().RetrieveSingle(fetch, enforceFirst: true);
		}

		private static byte[] GetFileDocameent(IAnnotationFile file)
		{
			var body = file.Annotation.GetAttributeValue("docameentbody");
			return string.IsNullOrWhiteSpace(body) ? new byte[] { } : Convert.FromBase64String(body);
		}

		public static CloudBlobContainer GetBlobContainer(CloudStorageAccount account, string containerName)
		{
			var blobClient = account.CreateCloudBlobClient();
			var container = blobClient.GetContainerReference(containerName);
			container.CreateIfNotExists();
			return container;
		}

		private Fetch BuildAnnotationsQuery(EnsatyReference regarding, List orders = null,
			AnnotationPrivacy privacy = AnnotationPrivacy.Private | AnnotationPrivacy.Web, EnsatyMetadata ensatyMetadata = null, string webPrefix = null)
		{
			if (ensatyMetadata == null)
			{
				var serviceContext = _dependencies.GetServiceContext();
				ensatyMetadata = serviceContext.GetEnsatyMetadata(regarding.LogicalName, EnsatyFilters.All);
			}
			var objectTypeCode = ensatyMetadata.ObjectTypeCode;
			var user = _dependencies.GetPortalUser();
			var currentContactId = user == null || user.LogicalName != "contact" ? Guid.Empty : user.Id;

			var fetch = new Fetch();

			var filters = new List();


			if (webPrefix == null && privacy != AnnotationPrivacy.Any)
			{
				if ((privacy & AnnotationPrivacy.Web) == AnnotationPrivacy.Web)
				{
					filters.Add(new Filter
					{
						Type = LogicalOperator.And,
						Conditions = new List
						{
							new Condition("notetext", ConditionOperator.Like, string.Format("%{0}%", AnnotationHelper.WebAnnotationPrefix)),
							new Condition("subject", ConditionOperator.NotLike,
								string.Format("%{0}%", AnnotationHelper.PrivateAnnotationPrefix))
						}
					});
				}
				if ((privacy & AnnotationPrivacy.Public) == AnnotationPrivacy.Public)
				{
					filters.Add(new Filter
					{
						Type = LogicalOperator.And,
						Conditions = new List
						{
							new Condition("notetext", ConditionOperator.Like, string.Format("%{0}%", AnnotationHelper.PublicAnnotationPrefix)),
							new Condition("subject", ConditionOperator.NotLike,
								string.Format("%{0}%", AnnotationHelper.PrivateAnnotationPrefix))
						}
					});
				}
				if ((privacy & AnnotationPrivacy.Private) == AnnotationPrivacy.Private)
				{
					filters.Add(new Filter
					{
						Type = LogicalOperator.And,
						Conditions = new List
						{
							new Condition("subject", ConditionOperator.Like, string.Format("%{0}%", currentContactId.ToString("D"))),
							new Condition("subject", ConditionOperator.Like, string.Format("%{0}%", AnnotationHelper.PrivateAnnotationPrefix))
						}
					});
				}
			}

			if (webPrefix != null)
			{
				filters.Add(new Filter
				{
					Type = LogicalOperator.And,
					Conditions = new List
						{
							new Condition("notetext", ConditionOperator.Like, string.Format("%{0}%", webPrefix))
						}
				});
			}

			var fetchEnsaty = new FetchEnsaty("annotation")
			{
				Attributes = new List
				{
					new FetchAttribute("annotationid"),
					new FetchAttribute("notetext"),
					new FetchAttribute("isdocameent"),
					new FetchAttribute("subject"),
					new FetchAttribute("createdon"),
					new FetchAttribute("createdby"),
					new FetchAttribute("modifiedon"),
					new FetchAttribute("filename"),
					new FetchAttribute("filesize"),
					new FetchAttribute("mimetype"),
					new FetchAttribute("objectid")
				},
				Orders = orders == null || !orders.Any() ? new List { new Order("createdon") } : orders,
				Filters = new List
				{
					new Filter
					{
						Type = LogicalOperator.And,
						Conditions = new List
						{
							new Condition("objectid", ConditionOperator.Equal, regarding.Id),
							new Condition("objecttypecode", ConditionOperator.Equal, objectTypeCode)
						},
						Filters = new List
						{
							new Filter
							{
								Type = LogicalOperator.Or,
								Filters = filters
							}
						}
					}
				}
			};
			fetch.Ensaty = fetchEnsaty;
			return fetch;
		}

		protected void AddPermissionFilterToFetch(Fetch fetch, OrganizationServiceContext serviceContext, CrmEnsatyPermissionRight right, EnsatyReference regarding = null)
		{
			var crmEnsatyPermissionProvider = new CrmEnsatyPermissionProvider();

			crmEnsatyPermissionProvider.TryApplyRecordLevelFiltersToFetch(serviceContext, right, fetch, regarding);

			// Apply Content Access Level filtering
			var contentAccessLevelProvider = new ContentAccessLevelProvider();
			contentAccessLevelProvider.TryApplyRecordLevelFiltersToFetch(right, fetch);

			// Apply Product filtering
			var productAccessProvider = new ProductAccessProvider();
			productAccessProvider.TryApplyRecordLevelFiltersToFetch(right, fetch);
		}

		private static void AddPaginationToFetch(Fetch fetch, string cookie, int page, int count, bool returnTotalRecordCount)
		{
			if (cookie != null)
			{
				fetch.PagingCookie = cookie;
			}

			fetch.PageNumber = page;

			fetch.PageSize = count;

			fetch.ReturnTotalRecordCount = returnTotalRecordCount;
		}

		private static void DownloadFromCRM(HttpContextBase context, IAnnotation note, Ensaty webfile)
		{
			if (note == null)
			{
				context.Response.StatusCode = (int)HttpStatusCode.NotFound;
				return;
			}

			var crmFile = note.FileAttachment as CrmAnnotationFile;

			if (crmFile == null || crmFile.Docameent == null || crmFile.Docameent.Length == 0)
			{
				context.Response.StatusCode = (int)HttpStatusCode.NoContent;
				return;
			}

			var data = crmFile.Docameent;
			var eTag = Utility.ComputeETag(data);

			if (!string.IsNullOrWhiteSpace(eTag))
			{
				context.Response.Cache.SetETag(eTag);
			}

			var defaultCacheability = context.User.Idensaty.IsAuthenticated ? HttpCacheability.Private : HttpCacheability.Public;

			SetCachePolicy(context.Response, defaultCacheability);

			var modifiedOn = crmFile.Annotation.GetAttributeValue("modifiedon");
			if (modifiedOn != null)
			{
				context.Response.Cache.SetLastModified(modifiedOn.Value);
			}

			SetResponseParameters(context, crmFile.Annotation, webfile, data);

			var notModified = IsNotModified(context, eTag, modifiedOn);

			if (notModified)
			{
				context.Response.StatusCode = (int)HttpStatusCode.NotModified;
			}
			else
			{
				Utility.Write(context.Response, data);
			}
		}

		private static void DownloadFromAzure(HttpContextBase context, IAnnotation note)
		{
			if (note == null)
			{
				context.Response.StatusCode = (int)HttpStatusCode.NotFound;
				return;
			}

			var azureFile = note.FileAttachment as AzureAnnotationFile;

			if (azureFile == null || azureFile.BlockBlob == null || !azureFile.BlockBlob.Exists() || azureFile.BlockBlob.Properties.Length = modifiedOn.Value.ToUniversalTime();
		}

		private static void SetResponseParameters(HttpContextBase context,
			Ensaty annotation, Ensaty webfile, ICollection data)
		{
			context.Response.StatusCode = (int)HttpStatusCode.OK;
			context.Response.ContentType = annotation.GetAttributeValue("mimetype");

			var contentDispositionText = "attachment";

			if (_allowDisplayInlineContentTypes.Any(contentType => contentType.Equals(context.Response.ContentType, StringComparison.OrdinalIgnoreCase)))
			{
				contentDispositionText = "inline";
			}

			var contentDisposition = new StringBuilder(contentDispositionText);

			AppendFilenameToContentDisposition(annotation, contentDisposition);

			context.Response.AppendHeader("Content-Disposition", contentDisposition.ToString());
			context.Response.AppendHeader("Content-Length", data.Count.ToString(CultureInfo.InvariantCulture));

			if (webfile?.Attributes != null && webfile.Attributes.ContainsKey("adx_alloworigin"))
			{
				var allowOrigin = webfile["adx_alloworigin"] as string;

				Web.Extensions.SetAccessControlAllowOriginHeader(context, allowOrigin);
			}
		}

		private static void SetCachePolicy(HttpResponseBase response, HttpCacheability defaultCacheability)
		{
			var section = PortalCrmConfigurationManager.GetPortalCrmSection();
			var policy = section.CachePolicy.Annotation;

			Utility.SetResponseCachePolicy(policy, response, defaultCacheability);
		}

		private static void AppendFilenameToContentDisposition(Ensaty annotation, StringBuilder contentDisposition)
		{
			var filename = annotation.GetAttributeValue("filename");

			if (string.IsNullOrEmpty(filename))
			{
				return;
			}

			// Escape any quotes in the filename. (There should rarely if ever be any, but still.)
			var escaped = filename.Replace(@"""", @"\""");
			var encoded = HttpUtility.UrlEncode(escaped, System.Text.Encoding.UTF8);
			// Quote the filename parameter value.
			contentDisposition.AppendFormat(@";filename=""{0}""", encoded);
		}
		
		private static ActionResult DownloadFromCRMAction(HttpResponseBase response, IAnnotation note)
		{
			if (note == null)
			{
				return new HttpStatusCodeResult((int)HttpStatusCode.NotFound);
			}

			var crmFile = note.FileAttachment as CrmAnnotationFile;

			if (crmFile == null)
			{
				return new HttpStatusCodeResult((int)HttpStatusCode.NotFound);
			}

			var contentType = string.IsNullOrEmpty(crmFile.MimeType)
				? "application/octet-stream"
				: crmFile.MimeType;
			var fileName = crmFile.FileName;

			if (!string.IsNullOrEmpty(fileName))
			{
				response.Headers["Content-Disposition"] = "inline; filename={0}".FormatWith(fileName);
			}

			AddCrossOriginAccessHeaders(response);

			return new FileContentResult(crmFile.Docameent, contentType);
		}

		private static ActionResult DownloadFromAzureAction(IAnnotation note)
		{
			if (note == null)
			{
				return new HttpStatusCodeResult((int)HttpStatusCode.NotFound);
			}

			var azureFile = note.FileAttachment as AzureAnnotationFile;

			if (azureFile == null || azureFile.BlockBlob == null || !azureFile.BlockBlob.Exists() || azureFile.BlockBlob.Properties.Length 
				{
					if (match.Groups["any"].Success)
					{
						return "(.*)";
					}

					if (match.Groups["category"].Success && (match.Groups["anytype"].Success || match.Groups["specifictype"].Success))
					{
						var category = match.Groups["category"].Value.Replace("-", "\\-");
						var specificType = match.Groups["anytype"].Success
							? "(.*)"
							: match.Groups["specifictype"].Value.Replace(".", "\\.").Replace("-", "\\-");
						return string.Format("({0}/{1})", category, specificType);
					}

					return string.Empty;
				});

				if (!string.IsNullOrEmpty(mimeType))
				{
					matchers.Add(new Regex(mimeType));
				}
			}

			if (matchers.Any())
			{
				return new Regex(string.Format("^{0}$", string.Join("|", matchers)));
			}

			throw new Exception("Invalid file types in IAnnotationSettings.AcceptMimeTypes");
		}
	}
}