// ************************************************************* // Workaround to maintain cache integrity in authorization token revoke scenario public void RemoveAuthorizationTokenFromCache(ActivityDO activity) { lock (_loadedNodes) { foreach (var loadedPlan in _loadedPlans) { PlanTreeHelper.Visit(loadedPlan.Root, x => { var a = x as ActivityDO; if (a != null && a.Id == activity.Id) { a.AuthorizationToken = null; a.AuthorizationTokenId = null; } }); } } _planStorage.UpdateElement(activity.Id, x => { var a = x as ActivityDO; if (a != null) { a.AuthorizationToken = null; a.AuthorizationTokenId = null; } }); }
/**********************************************************************************/ public TPlanNode GetById <TPlanNode>(Guid id) where TPlanNode : PlanNodeDO { lock (_loadedNodes) { LoadedPlan loadedPlan; // if we have loaded this node before? if (!_loadedNodes.TryGetValue(id, out loadedPlan)) { // try to load plan for this node var plan = GetPlanByMemberId(id); // non existent node or new node that has not been saved yet if (plan == null) { return(null); } loadedPlan = new LoadedPlan(plan); _loadedPlans.Add(loadedPlan); // add all noded to the loaded nodes list PlanTreeHelper.Visit(plan, x => _loadedNodes.Add(x.Id, loadedPlan)); } // search for the node in the corresponding plans return((TPlanNode)loadedPlan.Find(id)); } }
/**********************************************************************************/ private void AddToCache(PlanNodeDO root) { var expirOn = _expirationStrategy.NewExpirationToken(); var cachedPlan = new CachedPlan(root, expirOn); _plans.Add(root.Id, cachedPlan); PlanTreeHelper.Visit(root, x => _planNodesLookup.Add(x.Id, new CacheItem(x, cachedPlan))); }
/**********************************************************************************/ public PlanSnapshot(PlanNodeDO node, bool cloneNodes) { PlanTreeHelper.Visit(node, (x, y) => { var clone = cloneNodes ? x.Clone() : x; _nodes[x.Id] = clone; clone.ParentPlanNodeId = y != null ? y.Id : (Guid?)null; }); }
/**********************************************************************************/ private void DropCachedPlan(PlanNodeDO root) { CachedPlan cachedPlan; if (!_plans.TryGetValue(root.Id, out cachedPlan)) { return; } PlanTreeHelper.Visit(root, x => _planNodesLookup.Remove(x.Id)); _plans.Remove(root.Id); }
/**********************************************************************************/ private void RemoveExpiredPlans() { lock (_sync) { foreach (var planExpiration in _plans.ToArray()) { if (planExpiration.Value.Expiration.IsExpired()) { _plans.Remove(planExpiration.Key); PlanTreeHelper.Visit(planExpiration.Value.Root, x => _planNodesLookup.Remove(x.Id)); } } } }
/**********************************************************************************/ // this is just simplification for the first implementation. // We can only insert plans. If we want to edit plan, we need to get corresponding node and edit it's children public void Add(PlanDO plan) { lock (_loadedNodes) { var loadedPlan = new LoadedPlan(plan, true); PlanTreeHelper.Visit(plan, x => { if (x.Id == Guid.Empty) { x.Id = Guid.NewGuid(); } _loadedNodes.Add(x.Id, loadedPlan); }); _loadedPlans.Add(loadedPlan); } }
/**********************************************************************************/ public PlanNodeDO Get(Guid id, Func <Guid, PlanNodeDO> cacheMissCallback) { PlanNodeDO node; lock (_sync) { CacheItem cacheItem; if (!_planNodesLookup.TryGetValue(id, out cacheItem)) { node = cacheMissCallback(id); if (node == null) { return(null); } // Get the root of PlanNode tree. while (node.ParentPlanNode != null) { node = node.ParentPlanNode; } // Check cache integrity if (PlanTreeHelper.Linearize(node).Any(x => _planNodesLookup.ContainsKey(x.Id))) { DropCachedPlan(node); } AddToCache(node); } else { node = cacheItem.Plan.Root; // update plan expiration cacheItem.Plan.Expiration = _expirationStrategy.NewExpirationToken(); } node = PlanTreeHelper.CloneWithStructure(node); } return(node); }
/**********************************************************************************/ public void SaveChanges() { lock (_loadedNodes) { foreach (var loadedPlan in _loadedPlans) { var plan = loadedPlan; plan.Root.LastUpdated = DateTimeOffset.UtcNow; var parentPlan = loadedPlan.Root as PlanDO; PlanTreeHelper.Visit(loadedPlan.Root, (x, y) => { if (x.Id == Guid.Empty) { x.Id = Guid.NewGuid(); } x.ParentPlanNode = y; x.ParentPlanNodeId = y != null ? y.Id : (Guid?)null; if (parentPlan != null) { x.Fr8AccountId = parentPlan.Fr8AccountId; x.Fr8Account = parentPlan.Fr8Account; x.RootPlanNodeId = parentPlan.Id; UpdateForeignKeys(x); } else { UpdateForeignKeys(x); } _loadedNodes[x.Id] = plan; }); var previous = loadedPlan.RebuildSnapshot(); var changes = loadedPlan.Snapshot.Compare(previous); _planStorage.Update(loadedPlan.Root.Id, changes); } } }
public PlanNodeDO Find(Guid nodeId) { if (IsDeleted) { return(null); } PlanNodeDO result = null; PlanTreeHelper.Visit(Root, x => { if (x.Id == nodeId) { result = x; return(false); } return(true); }); return(result); }
/**********************************************************************************/ public PlanSnapshot.Changes Update(Guid planId, PlanSnapshot.Changes changes) { var validChanges = new PlanSnapshot.Changes(); lock (_sync) { CachedPlan plan; if (!_plans.TryGetValue(planId, out plan)) { foreach (var insert in changes.Insert) { var clone = insert.Clone(); if (insert is PlanDO) { plan = new CachedPlan((PlanDO)clone, _expirationStrategy.NewExpirationToken()); _plans.Add(planId, plan); _planNodesLookup.Add(planId, new CacheItem(clone, plan)); clone.RootPlanNode = clone; break; } } } foreach (var insert in changes.Insert) { if (!_planNodesLookup.ContainsKey(insert.Id)) { if (insert.ParentPlanNodeId == null || _planNodesLookup.ContainsKey(insert.ParentPlanNodeId.Value)) { _planNodesLookup.Add(insert.Id, new CacheItem(insert.Clone(), plan)); } } } foreach (var insert in changes.Insert) { CacheItem nodeCacheItem; if (!_planNodesLookup.TryGetValue(insert.Id, out nodeCacheItem)) { continue; } validChanges.Insert.Add(insert); var node = nodeCacheItem.Node; if (insert.ParentPlanNodeId != null) { var parent = _planNodesLookup[insert.ParentPlanNodeId.Value].Node; parent.ChildNodes.Add(node); node.ParentPlanNode = parent; node.RootPlanNode = plan.Root; } } foreach (var deleted in changes.Delete) { CachedPlan cachedPlan; if (_plans.TryGetValue(deleted.Id, out cachedPlan)) { PlanTreeHelper.Visit(plan.Root, x => { _planNodesLookup.Remove(x.Id); validChanges.Delete.Add(x); }); _plans.Remove(plan.Root.Id); return(validChanges); } CacheItem node; if (_planNodesLookup.TryGetValue(deleted.Id, out node)) { validChanges.Delete.Add(deleted); _planNodesLookup.Remove(deleted.Id); node.Node.RemoveFromParent(); } } foreach (var update in changes.Update) { bool approveUpdate = false; foreach (var changedProperty in update.ChangedProperties) { CacheItem originalCacheItem; if (!_planNodesLookup.TryGetValue(update.Node.Id, out originalCacheItem)) { continue; } var original = originalCacheItem.Node; // structure was changed if (changedProperty.Name == "ParentPlanNodeId") { CacheItem parentCacheItem; if (update.Node.ParentPlanNodeId == null || !_planNodesLookup.TryGetValue(update.Node.ParentPlanNodeId.Value, out parentCacheItem)) { continue; } var parent = parentCacheItem.Node; original.RemoveFromParent(); parent.ChildNodes.Add(original); original.ParentPlanNode = parent; original.ParentPlanNodeId = parent.Id; approveUpdate = true; } else { approveUpdate = true; changedProperty.SetValue(original, changedProperty.GetValue(update.Node)); } } if (approveUpdate) { validChanges.Update.Add(update); } } } return(validChanges); }
/**********************************************************************************/ // This method updates locally cached elements from global cache or DB // This method will overwrite all local changes public TPlanNode Reload <TPlanNode>(Guid id) where TPlanNode : PlanNodeDO { var planFromDb = GetPlanByMemberId(id); lock (_loadedNodes) { // have we already loaded this plan? var loadedPlan = _loadedPlans.FirstOrDefault(x => x.Root.Id == planFromDb.Id); if (loadedPlan == null) { //if no, then just get this plan by id return(GetById <TPlanNode>(id)); } // get list of currently loaded items var currentNodes = PlanTreeHelper.Linearize(loadedPlan.Root).ToDictionary(x => x.Id, x => x); var dbNodes = PlanTreeHelper.Linearize(planFromDb).ToDictionary(x => x.Id, x => x); foreach (var planNodeDo in dbNodes) { PlanNodeDO currentNode; // sync structure var originalChildren = planNodeDo.Value.ChildNodes; planNodeDo.Value.ChildNodes = new List <PlanNodeDO>(originalChildren.Count); foreach (var childNode in originalChildren) { planNodeDo.Value.ChildNodes.Add(GetNewNodeOrGetExising(currentNodes, childNode)); } planNodeDo.Value.ParentPlanNode = GetNewNodeOrGetExising(currentNodes, planNodeDo.Value.ParentPlanNode); planNodeDo.Value.RootPlanNode = GetNewNodeOrGetExising(currentNodes, planNodeDo.Value.RootPlanNode); if (currentNodes.TryGetValue(planNodeDo.Key, out currentNode)) { //sync local cached properties with db one currentNode.SyncPropertiesWith(planNodeDo.Value); currentNode.ChildNodes = planNodeDo.Value.ChildNodes; currentNode.ParentPlanNode = planNodeDo.Value.ParentPlanNode; currentNode.RootPlanNode = planNodeDo.Value.RootPlanNode; } else // we don't have this node in our local cache. { _loadedNodes[planNodeDo.Key] = loadedPlan; } } // remove nodes, that we deleted in the DB version foreach (var planNodeDo in currentNodes) { if (!dbNodes.ContainsKey(planNodeDo.Key)) { _loadedNodes.Remove(planNodeDo.Key); } } loadedPlan.RebuildSnapshot(); return((TPlanNode)loadedPlan.Find(id)); } }