/ToMigrate/Bundles/Raven.Bundles.UniqueConstraints/UniqueConstraintsPutTrigger.cs

https://github.com/fitzchak/ravendb · C# · 283 lines · 214 code · 66 blank · 3 comment · 45 complexity · 5842ad9a722dd95880ed0f1eda66ff4e MD5 · raw file

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Text;
  4. using Raven.Abstractions.Data;
  5. using Raven.Database.Plugins;
  6. using Raven.Json.Linq;
  7. namespace Raven.Bundles.UniqueConstraints
  8. {
  9. public class UniqueConstraintsPutTrigger : AbstractPutTrigger
  10. {
  11. public override void AfterPut(string key, RavenJObject document, RavenJObject metadata, Etag etag)
  12. {
  13. if (key.StartsWith("Raven/"))
  14. {
  15. return;
  16. }
  17. var entityName = metadata.Value<string>(Constants.RavenEntityName) + "/";
  18. var properties = metadata.Value<RavenJArray>(Constants.EnsureUniqueConstraints);
  19. if (properties == null || properties.Length <= 0)
  20. return;
  21. foreach (var property in properties)
  22. {
  23. var constraint = Util.GetConstraint(property);
  24. var prop = document[constraint.PropName];
  25. string[] uniqueValues;
  26. if (!Util.TryGetUniqueValues(prop, out uniqueValues))
  27. continue;
  28. var prefix = "UniqueConstraints/" + entityName + constraint.PropName + "/";
  29. foreach (var uniqueValue in uniqueValues)
  30. {
  31. var escapedUniqueValue = Util.EscapeUniqueValue(uniqueValue, constraint.CaseInsensitive);
  32. var uniqueConstraintsDocumentKey = prefix + escapedUniqueValue;
  33. var uniqueConstraintsDocument = Database.Documents.Get(uniqueConstraintsDocumentKey);
  34. if (uniqueConstraintsDocument != null)
  35. {
  36. uniqueConstraintsDocument = DeepCloneDocument(uniqueConstraintsDocument);
  37. ConvertUniqueConstraintsDocumentIfNecessary(uniqueConstraintsDocument, escapedUniqueValue); // backward compatibility
  38. }
  39. else
  40. uniqueConstraintsDocument = new JsonDocument();
  41. AddConstraintToUniqueConstraintsDocument(uniqueConstraintsDocument, escapedUniqueValue, key);
  42. uniqueConstraintsDocument.Metadata[Constants.IsConstraintDocument] = true;
  43. Database.Documents.Put(
  44. uniqueConstraintsDocumentKey,
  45. null,
  46. uniqueConstraintsDocument.DataAsJson,
  47. uniqueConstraintsDocument.Metadata);
  48. }
  49. }
  50. }
  51. public override VetoResult AllowPut(string key, RavenJObject document, RavenJObject metadata)
  52. {
  53. if (key.StartsWith("Raven/"))
  54. {
  55. return VetoResult.Allowed;
  56. }
  57. var entityName = metadata.Value<string>(Constants.RavenEntityName);
  58. if (string.IsNullOrEmpty(entityName))
  59. {
  60. return VetoResult.Allowed;
  61. }
  62. entityName += "/";
  63. var properties = metadata.Value<RavenJArray>(Constants.EnsureUniqueConstraints);
  64. if (properties == null || properties.Length <= 0)
  65. return VetoResult.Allowed;
  66. var invalidFields = new StringBuilder();
  67. foreach (var property in properties)
  68. {
  69. var constraint = Util.GetConstraint(property);
  70. var prefix = "UniqueConstraints/" + entityName + constraint.PropName+ "/";
  71. var prop = document[constraint.PropName];
  72. string[] uniqueValues;
  73. if (!Util.TryGetUniqueValues(prop, out uniqueValues))
  74. continue;
  75. foreach (var uniqueValue in uniqueValues)
  76. {
  77. var escapedUniqueValue = Util.EscapeUniqueValue(uniqueValue, constraint.CaseInsensitive);
  78. var checkDocKey = prefix + escapedUniqueValue;
  79. var checkDoc = Database.Documents.Get(checkDocKey);
  80. if (checkDoc == null)
  81. continue;
  82. var checkId = GetRelatedIdFromUniqueConstraintsDocument(checkDoc, escapedUniqueValue);
  83. if (!string.IsNullOrEmpty(checkId) && checkId != key)
  84. invalidFields.Append(constraint.PropName + ", ");
  85. }
  86. }
  87. if (invalidFields.Length > 0)
  88. {
  89. invalidFields.Length = invalidFields.Length - 2;
  90. return VetoResult.Deny("Ensure unique constraint violated for fields: " + invalidFields);
  91. }
  92. return VetoResult.Allowed;
  93. }
  94. public override void OnPut(string key, RavenJObject jsonReplicationDocument, RavenJObject metadata)
  95. {
  96. if (key.StartsWith("Raven/"))
  97. {
  98. return;
  99. }
  100. var entityName = metadata.Value<string>(Constants.RavenEntityName) + "/";
  101. var properties = metadata.Value<RavenJArray>(Constants.EnsureUniqueConstraints);
  102. if (properties == null || properties.Length <= 0)
  103. return;
  104. var oldDoc = Database.Documents.Get(key);
  105. if (oldDoc == null)
  106. {
  107. return;
  108. }
  109. foreach (var property in metadata.Value<RavenJArray>(Constants.EnsureUniqueConstraints))
  110. {
  111. var constraint = Util.GetConstraint(property);
  112. var newProp = jsonReplicationDocument[constraint.PropName];
  113. // Handle Updates in the constraint since it changed
  114. var prefix = "UniqueConstraints/" + entityName + constraint.PropName + "/";
  115. var oldProp = oldDoc.DataAsJson[constraint.PropName];
  116. string[] oldUniqueValues;
  117. if (!Util.TryGetUniqueValues(oldProp, out oldUniqueValues))
  118. continue;
  119. string[] newUniqueValues;
  120. if (Util.TryGetUniqueValues(newProp, out newUniqueValues))
  121. {
  122. var join = (from oldValue in oldUniqueValues
  123. join newValue in newUniqueValues
  124. on oldValue equals newValue
  125. select oldValue);
  126. if (join.Count() == oldUniqueValues.Count())
  127. continue;
  128. }
  129. foreach (var oldUniqueValue in oldUniqueValues)
  130. {
  131. var escapedUniqueValue = Util.EscapeUniqueValue(oldUniqueValue, constraint.CaseInsensitive);
  132. var uniqueConstraintsDocumentKey = prefix + escapedUniqueValue;
  133. var uniqueConstraintsDocument = Database.Documents.Get(uniqueConstraintsDocumentKey);
  134. if (uniqueConstraintsDocument == null)
  135. continue;
  136. uniqueConstraintsDocument = DeepCloneDocument(uniqueConstraintsDocument);
  137. var removed = RemoveConstraintFromUniqueConstraintDocument(uniqueConstraintsDocument, escapedUniqueValue);
  138. if (ShouldRemoveUniqueConstraintDocument(uniqueConstraintsDocument))
  139. {
  140. Database.Documents.Delete(uniqueConstraintsDocumentKey, null);
  141. }
  142. else if (removed)
  143. {
  144. Database.Documents.Put(
  145. uniqueConstraintsDocumentKey, null,
  146. uniqueConstraintsDocument.DataAsJson,
  147. uniqueConstraintsDocument.Metadata);
  148. }
  149. }
  150. }
  151. }
  152. private static JsonDocument DeepCloneDocument(JsonDocument uniqueConstraintsDocument)
  153. {
  154. //This is a very expensive deep clone, i don't want to expose it as a method of JsonDocument.
  155. //The reason i do this is because Snapshoting is shallow and we need a deep snapshot.
  156. JsonDocument clone = new JsonDocument();
  157. clone.DataAsJson = (RavenJObject)(uniqueConstraintsDocument.DataAsJson.CloneToken());
  158. clone.Metadata = (RavenJObject)(uniqueConstraintsDocument.Metadata.CloneToken());
  159. return clone;
  160. }
  161. private static bool ShouldRemoveUniqueConstraintDocument(JsonDocument uniqueConstraintsDocument)
  162. {
  163. if (!uniqueConstraintsDocument.DataAsJson.ContainsKey("Constraints"))
  164. return true;
  165. if (uniqueConstraintsDocument.DataAsJson.Keys.Count == 0)
  166. return true;
  167. var constraints = (RavenJObject)uniqueConstraintsDocument.DataAsJson["Constraints"];
  168. if (constraints.Keys.Count == 0)
  169. return true;
  170. return false;
  171. }
  172. private static void ConvertUniqueConstraintsDocumentIfNecessary(JsonDocument uniqueConstraintsDocument, string escapedUniqueValue)
  173. {
  174. var oldFormat = uniqueConstraintsDocument.DataAsJson.ContainsKey("RelatedId");
  175. if (oldFormat == false)
  176. return;
  177. var key = uniqueConstraintsDocument.DataAsJson.Value<string>("RelatedId");
  178. uniqueConstraintsDocument.DataAsJson.Remove("RelatedId");
  179. AddConstraintToUniqueConstraintsDocument(uniqueConstraintsDocument, escapedUniqueValue, key);
  180. }
  181. private static bool RemoveConstraintFromUniqueConstraintDocument(JsonDocument uniqueConstraintsDocument, string escapedUniqueValue)
  182. {
  183. if (uniqueConstraintsDocument.DataAsJson.ContainsKey("RelatedId"))
  184. return uniqueConstraintsDocument.DataAsJson.Remove("RelatedId");
  185. var constraints = (RavenJObject)uniqueConstraintsDocument.DataAsJson["Constraints"];
  186. return constraints.Remove(escapedUniqueValue);
  187. }
  188. private static void AddConstraintToUniqueConstraintsDocument(JsonDocument uniqueConstraintsDocument, string escapedUniqueValue, string key)
  189. {
  190. if (!uniqueConstraintsDocument.DataAsJson.ContainsKey("Constraints"))
  191. uniqueConstraintsDocument.DataAsJson["Constraints"] = new RavenJObject();
  192. var constraints = (RavenJObject)uniqueConstraintsDocument.DataAsJson["Constraints"];
  193. constraints[escapedUniqueValue] = RavenJObject.FromObject(new { RelatedId = key });
  194. }
  195. private static string GetRelatedIdFromUniqueConstraintsDocument(JsonDocument uniqueConstraintsDocument, string escapedUniqueValue)
  196. {
  197. if (uniqueConstraintsDocument.DataAsJson.ContainsKey("RelatedId"))
  198. return uniqueConstraintsDocument.DataAsJson.Value<string>("RelatedId");
  199. var constraints = (RavenJObject)uniqueConstraintsDocument.DataAsJson["Constraints"];
  200. RavenJToken value;
  201. if (constraints.TryGetValue(escapedUniqueValue, out value))
  202. return value.Value<string>("RelatedId");
  203. return null;
  204. }
  205. public override IEnumerable<string> GeneratedMetadataNames
  206. {
  207. get
  208. {
  209. return new[]
  210. {
  211. Constants.IsConstraintDocument,
  212. Constants.EnsureUniqueConstraints
  213. };
  214. }
  215. }
  216. }
  217. }