/// <summary>
        /// Applies the authorization filtering criteria to the query created by the decorated instance.
        /// </summary>
        /// <param name="specification">An instance of the entity representing the parameters to the query.</param>
        /// <param name="queryParameters">The parameter values to apply to the query.</param>
        /// <returns>The criteria created by the decorated instance.</returns>
        public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryParameters)
        {
            var criteria = _decoratedInstance.GetCriteriaQuery(specification, queryParameters);

            var authorizationFilters = _authorizationFilterContextProvider.GetFilterContext();

            if (authorizationFilters.FirstOrDefault() != null)
            {
                // This behavior was introduced to handle support for multiple EdOrg types, but this logic must handle all
                // authorizations performed. Currently, there are no other authorization strategies that use multiple claim types
                // so this is functional today, but would need to be revisited if such an authorization strategy was introduced.
                string[] distinctClaimEndpointNames =
                    authorizationFilters
                    .Select(s => s.ClaimEndpointName)
                    .Distinct()
                    .OrderBy(x => x)
                    .ToArray();

                bool hasMultipleClaimEndpoints = distinctClaimEndpointNames.Length > 1;

                var allFiltersGroupedBySubjectName = authorizationFilters.GroupBy(
                    x => x.SubjectEndpointName,
                    x => x);

                // ICriterions combined using AND
                var conjunction = new Conjunction();

                foreach (var subjectNameGrouping in allFiltersGroupedBySubjectName)
                {
                    // ICriterions combined using OR
                    var disjunction = new Disjunction();

                    bool isSubjectNameAuthorizable       = false;
                    var  unsupportedAuthorizationFilters = new List <string>();

                    foreach (var filterDetails in subjectNameGrouping)
                    {
                        IReadOnlyList <Action <ICriteria, Junction, IDictionary <string, object>, JoinType> > applicators;

                        if (!_authorizationCriteriaApplicatorProvider.TryGetCriteriaApplicator(
                                filterDetails.FilterName,
                                typeof(TEntity),
                                out applicators))
                        {
                            unsupportedAuthorizationFilters.Add(filterDetails.FilterName);

                            continue;
                        }

                        isSubjectNameAuthorizable = true;

                        var filtersBackedByNewAuthViews = new List <string>
                        {
                            "LocalEducationAgencyIdToStudentUSI",
                            "SchoolIdToStudentUSI",
                            "LocalEducationAgencyIdToParentUSI",
                            "ParentUSIToSchoolId",
                            "LocalEducationAgencyIdToStaffUSI",
                            "SchoolIdToStaffUSI"
                        };


                        // Invoke the filter applicators against the current query
                        foreach (var applicator in applicators)
                        {
                            Dictionary <string, object> parameterValues;

                            if (filtersBackedByNewAuthViews.Contains(filterDetails.FilterName, StringComparer.OrdinalIgnoreCase))
                            {
                                parameterValues =
                                    new Dictionary <string, object> {
                                    { "SourceEducationOrganizationId", filterDetails.ClaimValues }
                                };
                            }
                            else
                            {
                                parameterValues = new Dictionary <string, object>
                                {
                                    { filterDetails.ClaimEndpointName, filterDetails.ClaimValues }
                                };
                            }

                            applicator(criteria, disjunction, parameterValues, hasMultipleClaimEndpoints ? JoinType.LeftOuterJoin : JoinType.InnerJoin);
                        }
                    }

                    if (!isSubjectNameAuthorizable)
                    {
                        if (_logger.IsDebugEnabled)
                        {
                            _logger.Debug($"Unable to authorize access to '{typeof(TEntity).FullName}' because none of the following authorization filters were defined: '{string.Join($"', '", unsupportedAuthorizationFilters)}'.");
                        }

                        throw new EdFiSecurityException(
                                  $"Unable to authorize the request because there is no authorization support for associating the "
                                  + $"API client's associated claim values (of '{string.Join("', '", distinctClaimEndpointNames)}') with the requested resource ('{typeof(TEntity).Name}').");
                    }

                    conjunction.Add(disjunction);
                }

                criteria.Add(conjunction);
            }
            return(criteria);
        }
        /// <summary>
        /// Applies the authorization filtering criteria to the query created by the decorated instance.
        /// </summary>
        /// <param name="specification">An instance of the entity representing the parameters to the query.</param>
        /// <param name="queryParameters">The parameter values to apply to the query.</param>
        /// <returns>The criteria created by the decorated instance.</returns>
        public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryParameters)
        {
            var criteria = _decoratedInstance.GetCriteriaQuery(specification, queryParameters);

            var authorizationFiltering = _authorizationFilterContextProvider.GetFilterContext();

            var unsupportedAuthorizationFilters = new HashSet <string>();

            // Create the "AND" junction
            var mainConjunction = new Conjunction();

            // Create the "OR" junction
            var mainDisjunction = new Disjunction();

            // If there are multiple authorization strategies with views, we must use left outer joins and null/not null checks
            var joinType = DetermineJoinType();

            bool conjunctionFiltersWereApplied = ApplyAuthorizationStrategiesCombinedWithAndLogic();
            bool disjunctionFiltersWereApplied = ApplyAuthorizationStrategiesCombinedWithOrLogic();

            ApplyJunctionsToCriteriaQuery();

            return(criteria);

            JoinType DetermineJoinType()
            {
                var countOfAuthorizationFiltersWithViewBasedFilters = authorizationFiltering.Count(
                    af => af.Filters.Select(afd =>
                {
                    if (_authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition(afd.FilterName, out var filterDetails))
                    {
                        return(filterDetails);
                    }
                    ;

                    unsupportedAuthorizationFilters.Add(afd.FilterName);

                    return(null);
                })
                    .Where(x => x != null)
                    .OfType <ViewBasedAuthorizationFilterDefinition>()
                    .Any());

                return(countOfAuthorizationFiltersWithViewBasedFilters > 1
                    ? JoinType.LeftOuterJoin
                    : JoinType.InnerJoin);
            }

            bool ApplyAuthorizationStrategiesCombinedWithAndLogic()
            {
                var andStrategies = authorizationFiltering.Where(x => x.Operator == FilterOperator.And).ToArray();

                // Combine 'AND' strategies
                bool conjunctionFiltersApplied = false;

                foreach (var andStrategy in andStrategies)
                {
                    if (!TryApplyFilters(mainConjunction, andStrategy.Filters))
                    {
                        // All filters for AND strategies must be applied, and if not, this is an error condition
                        throw new Exception($"The following authorization filters are not recognized: {string.Join(" ", unsupportedAuthorizationFilters)}");
                    }

                    conjunctionFiltersApplied = true;
                }

                return(conjunctionFiltersApplied);
            }

            bool ApplyAuthorizationStrategiesCombinedWithOrLogic()
            {
                var orStrategies = authorizationFiltering.Where(x => x.Operator == FilterOperator.Or).ToArray();

                // Combine 'OR' strategies
                bool disjunctionFiltersApplied = false;

                foreach (var orStrategy in orStrategies)
                {
                    var filtersConjunction = new Conjunction(); // Combine filters with 'AND'

                    if (TryApplyFilters(filtersConjunction, orStrategy.Filters))
                    {
                        mainDisjunction.Add(filtersConjunction);

                        disjunctionFiltersApplied = true;
                    }
                }

                // If we have some OR strategies with filters defined, but no filters were applied, this is an error condition
                if (orStrategies.SelectMany(s => s.Filters).Any() && !disjunctionFiltersApplied)
                {
                    throw new Exception($"The following authorization filters are not recognized: {string.Join(" ", unsupportedAuthorizationFilters)}");
                }

                return(disjunctionFiltersApplied);
            }

            bool TryApplyFilters(Conjunction conjunction, IReadOnlyList <AuthorizationFilterContext> filters)
            {
                bool allFiltersCanBeApplied = true;

                foreach (var filterDetails in filters)
                {
                    if (!_authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition(
                            filterDetails.FilterName,
                            out var ignored))
                    {
                        unsupportedAuthorizationFilters.Add(filterDetails.FilterName);

                        allFiltersCanBeApplied = false;
                    }
                }

                if (!allFiltersCanBeApplied)
                {
                    return(false);
                }

                bool filtersApplied = false;

                foreach (var filterDetails in filters)
                {
                    _authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition(
                        filterDetails.FilterName,
                        out var filterApplicationDetails);

                    var applicator = filterApplicationDetails.CriteriaApplicator;

                    var parameterValues = new Dictionary <string, object>
                    {
                        { filterDetails.ClaimParameterName, filterDetails.ClaimParameterValues }
                    };

                    // Apply the authorization strategy filter
                    applicator(criteria, conjunction, parameterValues, joinType);

                    filtersApplied = true;
                }

                return(filtersApplied);
            }

            void ApplyJunctionsToCriteriaQuery()
            {
                if (disjunctionFiltersWereApplied)
                {
                    if (conjunctionFiltersWereApplied)
                    {
                        mainConjunction.Add(mainDisjunction);
                    }
                    else
                    {
                        criteria.Add(mainDisjunction);
                    }
                }

                if (conjunctionFiltersWereApplied)
                {
                    criteria.Add(mainConjunction);
                }
            }
        }