private void SetupProcessorFactoryForResourceDefinition <TModel>(Mock <IGenericServiceFactory> processorFactory, IResourceHookContainer <TModel> modelResource, IHooksDiscovery <TModel> discovery, AppDbContext dbContext = null, IResourceGraph resourceGraph = null) where TModel : class, IIdentifiable <int> { processorFactory.Setup(factory => factory.Get <IResourceHookContainer>(typeof(ResourceHooksDefinition <>), typeof(TModel))).Returns(modelResource); processorFactory.Setup(factory => factory.Get <IHooksDiscovery>(typeof(IHooksDiscovery <>), typeof(TModel))).Returns(discovery); if (dbContext != null) { Type idType = ObjectFactory.GetIdType(typeof(TModel)); if (idType == typeof(int)) { IResourceReadRepository <TModel, int> repo = CreateTestRepository <TModel>(dbContext, resourceGraph); processorFactory.Setup(factory => factory.Get <IResourceReadRepository <TModel, int> >(typeof(IResourceReadRepository <,>), typeof(TModel), typeof(int))).Returns(repo); } else { throw new TypeLoadException("Test not set up properly"); } } }
/// <inheritdoc /> public void BeforeRead <TResource>(ResourcePipeline pipeline, string stringId = null) where TResource : class, IIdentifiable { IResourceHookContainer <TResource> hookContainer = _containerProvider.GetResourceHookContainer <TResource>(ResourceHook.BeforeRead); hookContainer?.BeforeRead(pipeline, false, stringId); List <Type> calledContainers = typeof(TResource).AsList(); // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true IncludeExpression[] includes = _constraintProviders .SelectMany(provider => provider.GetConstraints()) .Select(expressionInScope => expressionInScope.Expression) .OfType <IncludeExpression>() .ToArray(); // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore foreach (ResourceFieldChainExpression chain in includes.SelectMany(IncludeChainConverter.GetRelationshipChains)) { RecursiveBeforeRead(chain.Fields.Cast <RelationshipAttribute>().ToList(), pipeline, calledContainers); } }
/// <summary> /// Given a source of resources, gets the implicitly affected resources from the database and calls the BeforeImplicitUpdateRelationship hook. /// </summary> private void FireForAffectedImplicits(Type resourceTypeToInclude, IDictionary <RelationshipAttribute, IEnumerable> implicitsTarget, ResourcePipeline pipeline, IEnumerable existingImplicitResources = null) { IResourceHookContainer container = _containerProvider.GetResourceHookContainer(resourceTypeToInclude, ResourceHook.BeforeImplicitUpdateRelationship); if (container == null) { return; } IDictionary <RelationshipAttribute, IEnumerable> implicitAffected = _containerProvider.LoadImplicitlyAffected(implicitsTarget, existingImplicitResources); if (!implicitAffected.Any()) { return; } Dictionary <RelationshipAttribute, IEnumerable> inverse = new Dictionary <RelationshipAttribute, IEnumerable>(); foreach (KeyValuePair <RelationshipAttribute, IEnumerable> pair in implicitAffected) { var inverseRelationship = _resourceGraph.GetInverseRelationship(pair.Key); if (inverseRelationship != null) { inverse.Add(inverseRelationship, pair.Value); } } IRelationshipsDictionary resourcesByRelationship = CreateRelationshipHelper(resourceTypeToInclude, inverse); CallHook(container, ResourceHook.BeforeImplicitUpdateRelationship, ArrayFactory.Create <object>(resourcesByRelationship, pipeline)); }
/// <summary> /// Recursively goes through the included relationships from JsonApiContext, translates them to the corresponding hook containers and fires the /// BeforeRead hook (if implemented) /// </summary> private void RecursiveBeforeRead(List <RelationshipAttribute> relationshipChain, ResourcePipeline pipeline, List <LeftType> calledContainers) { while (true) { RelationshipAttribute relationship = relationshipChain.First(); if (!calledContainers.Contains(relationship.RightType)) { calledContainers.Add(relationship.RightType); IResourceHookContainer container = _containerProvider.GetResourceHookContainer(relationship.RightType, ResourceHook.BeforeRead); if (container != null) { CallHook(container, ResourceHook.BeforeRead, new object[] { pipeline, true, null }); } } relationshipChain.RemoveAt(0); if (!relationshipChain.Any()) { break; } } }
/// <summary> /// For a given <see cref="ResourceHook"/> target and for a given type /// <typeparamref name="TResource"/>, gets the hook container if the target /// hook was implemented and should be executed. /// <para /> /// Along the way, creates a traversable node from the root resource set. /// </summary> /// <returns><c>true</c>, if hook was implemented, <c>false</c> otherwise.</returns> private bool GetHook <TResource>(ResourceHook target, IEnumerable <TResource> resources, out IResourceHookContainer <TResource> container, out RootNode <TResource> node) where TResource : class, IIdentifiable { node = _traversalHelper.CreateRootNode(resources); container = _executorHelper.GetResourceHookContainer <TResource>(target); return(container != null); }
/// <summary> /// For a given <see cref="ResourceHook" /> target and for a given type <typeparamref name="TResource" />, gets the hook container if the target hook was /// implemented and should be executed. /// <para /> /// Along the way, creates a traversable node from the root resource set. /// </summary> /// <returns> /// <c>true</c>, if hook was implemented, <c>false</c> otherwise. /// </returns> private GetHookResult <TResource> GetHook <TResource>(ResourceHook target, IEnumerable <TResource> resources) where TResource : class, IIdentifiable { RootNode <TResource> node = _nodeNavigator.CreateRootNode(resources); IResourceHookContainer <TResource> container = _containerProvider.GetResourceHookContainer <TResource>(target); return(new GetHookResult <TResource>(container, node)); }
/// <summary> /// A helper method to call a hook on <paramref name="container"/> reflectively. /// </summary> private IEnumerable CallHook(IResourceHookContainer container, ResourceHook hook, object[] arguments) { var method = container.GetType().GetMethod(hook.ToString("G")); // note that some of the hooks return "void". When these hooks, the // are called reflectively with Invoke like here, the return value // is just null, so we don't have to worry about casting issues here. return((IEnumerable)ThrowJsonApiExceptionOnError(() => method.Invoke(container, arguments))); }
/// <summary> /// Fires the AfterUpdateRelationship hook /// </summary> private void FireAfterUpdateRelationship(IResourceHookContainer container, IResourceNode node, ResourcePipeline pipeline) { Dictionary <RelationshipAttribute, IEnumerable> currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); // the relationships attributes in currentResourcesGrouped will be pointing from a // resource in the previous layer to a resource in the current (nested) layer. // For the nested hook we need to replace these attributes with their inverse. // See the FireNestedBeforeUpdateHooks method for a more detailed example. var resourcesByRelationship = CreateRelationshipHelper(node.ResourceType, ReplaceKeysWithInverseRelationships(currentResourcesGrouped)); CallHook(container, ResourceHook.AfterUpdateRelationship, new object[] { resourcesByRelationship, pipeline }); }
private void TraverseNextLayer(IEnumerable <IResourceNode> nextLayer, Action <IResourceHookContainer, IResourceNode> action, ResourceHook target) { foreach (IResourceNode node in nextLayer) { IResourceHookContainer hookContainer = _containerProvider.GetResourceHookContainer(node.ResourceType, target); if (hookContainer != null) { action(hookContainer, node); } } }
public GetHookResult(IResourceHookContainer <TResource> container, RootNode <TResource> node) { Container = container; Node = node; }
/// <summary> /// Fires the nested before hooks for resources in the current <paramref name="layer" /> /// </summary> /// <remarks> /// For example: consider the case when the owner of article1 (one-to-one) is being updated from owner_old to owner_new, where owner_new is currently /// already related to article2. Then, the following nested hooks need to be fired in the following order. First the BeforeUpdateRelationship should be /// for owner1, then the BeforeImplicitUpdateRelationship hook should be fired for owner2, and lastly the BeforeImplicitUpdateRelationship for article2. /// </remarks> private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, IEnumerable <IResourceNode> layer) { foreach (IResourceNode node in layer) { IResourceHookContainer nestedHookContainer = _containerProvider.GetResourceHookContainer(node.ResourceType, ResourceHook.BeforeUpdateRelationship); IEnumerable uniqueResources = node.UniqueResources; RightType resourceType = node.ResourceType; IDictionary <RelationshipAttribute, IEnumerable> currentResourcesGrouped; IDictionary <RelationshipAttribute, IEnumerable> currentResourcesGroupedInverse; // fire the BeforeUpdateRelationship hook for owner_new if (nestedHookContainer != null) { if (uniqueResources.Cast <IIdentifiable>().Any()) { RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(proxy => proxy.Attribute).ToArray(); IEnumerable dbValues = LoadDbValues(resourceType, uniqueResources, ResourceHook.BeforeUpdateRelationship, relationships); // these are the resources of the current node grouped by // RelationshipAttributes that occurred in the previous layer // so it looks like { HasOneAttribute:owner => owner_new }. // Note that in the BeforeUpdateRelationship hook of Person, // we want want inverse relationship attribute: // we now have the one pointing from article -> person, ] // but we require the the one that points from person -> article currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); currentResourcesGroupedInverse = ReplaceKeysWithInverseRelationships(currentResourcesGrouped); IRelationshipsDictionary resourcesByRelationship = CreateRelationshipHelper(resourceType, currentResourcesGroupedInverse, dbValues); IEnumerable <string> allowedIds = CallHook(nestedHookContainer, ResourceHook.BeforeUpdateRelationship, ArrayFactory.Create <object>(GetIds(uniqueResources), resourcesByRelationship, pipeline)).Cast <string>(); ISet <IIdentifiable> updated = GetAllowedResources(uniqueResources, allowedIds); node.UpdateUnique(updated); node.Reassign(); } } // Fire the BeforeImplicitUpdateRelationship hook for owner_old. // Note: if the pipeline is Post it means we just created article1, // which means we are sure that it isn't related to any other resources yet. if (pipeline != ResourcePipeline.Post) { // To fire a hook for owner_old, we need to first get a reference to it. // For this, we need to query the database for the HasOneAttribute:owner // relationship of article1, which is referred to as the // left side of the HasOneAttribute:owner relationship. IDictionary <RelationshipAttribute, IEnumerable> leftResources = node.RelationshipsFromPreviousLayer.GetLeftResources(); if (leftResources.Any()) { // owner_old is loaded, which is an "implicitly affected resource" FireForAffectedImplicits(resourceType, leftResources, pipeline, uniqueResources); } } // Fire the BeforeImplicitUpdateRelationship hook for article2 // For this, we need to query the database for the current owner // relationship value of owner_new. currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); if (currentResourcesGrouped.Any()) { // rightResources is grouped by relationships from previous // layer, ie { HasOneAttribute:owner => owner_new }. But // to load article2 onto owner_new, we need to have the // RelationshipAttribute from owner to article, which is the // inverse of HasOneAttribute:owner currentResourcesGroupedInverse = ReplaceKeysWithInverseRelationships(currentResourcesGrouped); // Note that currently in the JsonApiDotNetCore implementation of hooks, // the root layer is ALWAYS homogenous, so we safely assume // that for every relationship to the previous layer, the // left type is the same. LeftType leftType = currentResourcesGrouped.First().Key.LeftType; FireForAffectedImplicits(leftType, currentResourcesGroupedInverse, pipeline); } } }