Beispiel #1
0
        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");
                }
            }
        }
Beispiel #2
0
        /// <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);
            }
        }
Beispiel #3
0
        /// <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));
        }
Beispiel #4
0
        /// <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);
 }
Beispiel #6
0
        /// <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 });
        }
Beispiel #9
0
        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);
                }
            }
        }
Beispiel #10
0
 public GetHookResult(IResourceHookContainer <TResource> container, RootNode <TResource> node)
 {
     Container = container;
     Node      = node;
 }
Beispiel #11
0
        /// <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);
                }
            }
        }