Пример #1
0
        /// <summary>
        /// Build and return the console navigation tree.
        /// </summary>
        /// <returns>
        /// A <see cref="EntityData"/> containing the tree structure.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The <see cref="ReadiNow.IO.RequestContext"/> is not set.
        /// </exception>
        public EntityData GetTree()
        {
            if (!ReadiNow.IO.RequestContext.IsSet)
            {
                throw new InvalidOperationException("Request context not set");
            }

            EntityData   entityData;
            const string hintText = "navItems";

            using (new SecurityBypassContext())
                using (Profiler.Measure("ConsoleTreeRepository.GetTree"))
                {
                    // Construct and run the query
                    EntityRequest entityRequest = new EntityRequest
                    {
                        Entities          = new EntityRef(WellKnownAliases.CurrentTenant.TopMenu).ToEnumerable( ),
                        RequestString     = ConsoleTreeRequest,
                        Hint              = hintText,
                        IgnoreResultCache = true // this data is cached by the CachingConsoleTreeRepository
                    };
                    entityData = BulkRequestRunner.GetEntities(entityRequest).FirstOrDefault();

                    // It's not completely clear why we are using SecurityBypassContext and manually checking, rather than relying on the security model.
                    // One possible explanation is that the 'securesTo' flags cause default reports and picker reports to be readable, which may be undesirable in the UI

                    // If the user is not an administrator, remove entries without the "core:allowDisplay" relationship
                    // to either their own user account or a role they are in.
                    Prune(entityData, ReadiNow.IO.RequestContext.GetContext().Identity.Id);

                    return(entityData);
                }
        }
        IEnumerable <EntityData> IEntityInfoRead.GetEntitiesByType(EntityRef entityType, bool includeDerivedTypes, EntityMemberRequest request)
        {
            if (string.IsNullOrEmpty(request.RequestString))
            {
                Assert.Ignore("GetEntitiesByType not supported by BulkRequestRunner without request string");
            }
            else
            {
                var entityRequest = new EntityRequest
                {
                    QueryType         = includeDerivedTypes ? QueryType.Instances : QueryType.ExactInstances,
                    IgnoreResultCache = true,
                    Entities          = entityType.ToEnumerable(),
                    RequestString     = request.RequestString
                };


                // preferred path .. but still need to get away from passing EntityMemberRequest
                IEnumerable <EntityData> res = BulkRequestRunner.GetEntitiesByType(entityRequest);
                return(res);
            }


            return(null);
        }
        internal void RemoveUnreferencedWorkflowTraces()
        {
            var toDelete = BulkRequestRunner.FetchFilteredEntities("runTraceLogEntry", "[Workflow run] is null");

            if (toDelete.Count() > 0)
            {
                EventLog.Application.WriteInformation("Deleting {0} old workflow runs trace log entries", toDelete.Count());
                Entity.Delete(toDelete.Select(r => r.Id));
            }
        }
        public void Test_Can_Read(string field, string instance)
        {
            string request = field;

            EntityData res = BulkRequestRunner.GetEntityData(new EntityRef(instance), request);

            Assert.That(res, Is.Not.Null, "res");
            Assert.That(res.Fields, Has.Count.EqualTo(1));
            Assert.That(res.Fields[0].FieldId.Alias, Is.EqualTo(field));
            Assert.That(res.Fields[0].Value.Value, Is.EqualTo(true));
        }
Пример #5
0
        public void Test_CalculatedFieldsInBulkRequestRunner_FieldTypes(string fieldTypeAlias, string calc, string exected, Type databaseType, Type dataType)
        {
            var scenario = CreateScenario(fieldTypeAlias, calc);
            var field    = scenario.Item1;
            var inst     = scenario.Item2;

            string     request = "#" + field.Id.ToString();
            EntityData result  = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request);

            // Check results
            CheckResult(result, exected, databaseType, dataType);
        }
        public IEnumerable <EntityData> GetEntitiesData(IEnumerable <EntityRef> entities, EntityMemberRequest request, SecurityOption securityOption = SecurityOption.SkipDenied)
        {
            EntityRequest entityRequest = new EntityRequest
            {
                QueryType         = QueryType.Basic,
                IgnoreResultCache = true,
                Entities          = entities,
                Request           = request
            };

            return(BulkRequestRunner.GetEntitiesData(entityRequest));
        }
Пример #7
0
        /// <summary>
        /// Fetch a form as Entity data
        /// </summary>
        /// <param name="formId">The form identifier.</param>
        /// <param name="isInDesignMode">if set to <c>true</c> [is in design mode].</param>
        /// <returns></returns>
        /// <exception cref="FormNotFoundException">
        /// </exception>
        public static EntityData GetFormAsEntityData(long formId, bool isInDesignMode = false)
        {
            string request = CustomEditFormHelper.GetHtmlFormQuery(isInDesignMode);

            // Check security and existance
            try
            {
                IEntity form = Entity.Get(formId);
                if (form == null)
                {
                    EventLog.Application.WriteError("Attempted to fetch an unknown form/screen. {0}", formId);
                    throw new FormNotFoundException();
                }
            }
            catch (ArgumentException) // from EntityIdentificationCache.GetId
            {
                EventLog.Application.WriteError("Attempted to fetch an unknown form/screen. {0}", formId);
                throw new FormNotFoundException();
            }

            // Load form data
            EntityData formEntityData;

            using (Profiler.Measure("Get form data"))
                using (new SecurityBypassContext())
                {
                    formEntityData = BulkRequestRunner.GetEntityData(formId, request, "GetFormAsEntityData");
                }
            if (formEntityData == null)
            {
                EventLog.Application.WriteError("Attempted to fetch an unknown form. {0}", formId);
                throw new FormNotFoundException();
            }


            //
            // If we are not in design mode we need to go an fetch any missing default reports.
            // We don't want to fetch these for design mode because that will 'fix' them in the form.
            //
            if (!isInDesignMode)
            {
                using (Profiler.Measure("Get form default reports"))
                    using (new SecurityBypassContext())
                    {
                        SetDefaultReportsForRelationshipControls(formEntityData);
                    }
            }
            return(formEntityData);
        }
 public EntityData GetEntityData(EntityRef id, EntityMemberRequest request)
 {
     if (string.IsNullOrEmpty(request.RequestString))
     {
         // obsolete path
         var        results = GetEntitiesData(id.ToEnumerable(), request);
         EntityData res     = results.FirstOrDefault();
         return(res);
     }
     else
     {
         // preferred path .. but still need to get away from passing EntityMemberRequest
         EntityData res = BulkRequestRunner.GetEntityData(id, request.RequestString);
         return(res);
     }
 }
Пример #9
0
        /// <summary>
        ///     Pre-load data into the entity model cache according to the specified query.
        ///     Note: subsequent calls to preload take almost no time, unless the data queried has changed.
        ///     Caution: this mutates the entity request.
        /// </summary>
        /// <param name="request">Entity info request to preload.</param>
        public static void Preload(EntityRequest request)
        {
            using (Profiler.Measure(request.Hint ?? "Preload"))
                using (new SecurityBypassContext( ))
                {
                    // Check cache and/or get unsecured result
                    // Note: the results don't actually get secured at this point (we don't want to for the purpose of preload. So we're in a SecurityBypassContext)
                    // The reason we convert to entity data is because it's smart enough to fill in the empty fields/relationships for which there is no data.
                    // So we can cache the fact that those fields/relationships are empty. (BulkRequestResult alone doesn't give us this info).

                    request.DontProcessResultIfFromCache = true;

                    IEnumerable <EntityData> rootEntities;
                    if (request.QueryType == QueryType.ExactInstances || request.QueryType == QueryType.Instances)
                    {
                        rootEntities = BulkRequestRunner.GetEntitiesByType(request);
                    }
                    else
                    {
                        rootEntities = BulkRequestRunner.GetEntitiesData(request);
                    }

                    if (!request.ResultFromCache)
                    {
                        long isOfTypeId = WellKnownAliases.CurrentTenant.IsOfType;

                        // Flatten the graph
                        IEnumerable <EntityData> allEntityData = Delegates.WalkGraph(rootEntities, entityData => entityData.Relationships.SelectMany(relType => relType.Entities));

                        using (Entity.DistributedMemoryManager.Suppress( ))
                        {
                            // Extract field/relationship data and shove into cache
                            foreach (EntityData entityData in allEntityData)
                            {
                                AddEntityToCache(entityData, isOfTypeId);

                                AddFieldsToCache(entityData);

                                AddRelationshipsToCache(entityData);
                            }
                        }
                    }
                }
        }
Пример #10
0
        public void Test_Data_Changed()
        {
            var scenario = CreateScenario("core:stringField", "'Test'+Name");
            var field    = scenario.Item1;
            var inst     = scenario.Item2;

            string     request = "#" + field.Id.ToString();
            EntityData result  = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request);

            CheckResult(result, "TestName1", typeof(StringType), typeof(string));

            // Rename
            inst.Name = "NewName";
            inst.Save();

            result = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request);

            CheckResult(result, "TestNewName", typeof(StringType), typeof(string));
        }
        public void GetEntitiesFilteredNonDerived()
        {
            if (_runner == "EntityInfoService")
            {
                Assert.Ignore();
            }

            var request = new EntityRequest("core:report", "name", QueryType.FilterExactInstances, "Test");

            request.Filter = "[Name] = 'Inboxes'";

            IEnumerable <EntityData> results = BulkRequestRunner.GetEntities(request);

            Assert.That(results.Count(), Is.EqualTo(1));

            EntityData resource = results.Single();

            Assert.That(resource.Fields, Has.Count.EqualTo(1));
            Assert.That(resource.Fields[0].Value.ValueString, Is.EqualTo("Inboxes"));
        }
Пример #12
0
        /// <summary>
        ///     Add a request for a set of entities and queries.
        /// </summary>
        /// <param name="batch">The batch.</param>
        /// <param name="hintText">A hint about what this query is doing. Use for logging/diagnostics only.</param>
        /// <exception cref="System.ArgumentException">
        /// </exception>
        /// <exception cref="System.Exception">Internal error: batch query mismatch.</exception>
        public void AddEntityRequestBatch(JsonQueryBatchRequest batch, string hintText = null)
        {
            JsonQueryResult result = _result;


#pragma warning disable 618
            EntityInfoService svc = _svc;
#pragma warning restore 618

            var memberRequests = new EntityMemberRequest[batch.Queries.Length];

            foreach (JsonQuerySingleRequest request in batch.Requests)
            {
                string requestHintText = request.Hint ?? hintText;

                _entitiesVisited.Clear( );

                var singleResult = new JsonSingleQueryResult
                {
                    Hint = request.Hint, Ids = new List <long>( )
                };

                try
                {
                    EntityMemberRequest memberRequest;

                    // Do not require access to the individual query components
                    using (PerformanceCounters.Measure(EntityInfoServicePerformanceCounters.CategoryName, EntityInfoServicePerformanceCounters.RequestCountersPrefix))
                        using (new SecurityBypassContext( ))
                        {
                            // Get/parse the request query. (Either parse, or load from previous cache as part of this request).
                            if (request.QueryIndex < 0 || request.QueryIndex >= batch.Queries.Length)
                            {
                                EventLog.Application.WriteError("Cannot locate query string.");
                                singleResult.Code = HttpStatusCode.BadRequest;
                                continue;
                            }
                            memberRequest = memberRequests[request.QueryIndex];
                            if (memberRequest == null)
                            {
                                string queryString = batch.Queries[request.QueryIndex];

                                //// Parse the request
                                try
                                {
                                    memberRequest = Factory.RequestParser.ParseRequestQuery(queryString);
                                }
                                catch (Exception ex)
                                {
                                    EventLog.Application.WriteError(ex.ToString( ));
                                    singleResult.Code = HttpStatusCode.BadRequest;
                                    continue;
                                }
                                memberRequests[request.QueryIndex] = memberRequest;
                            }
                        }

                    // Get the entityRefs
                    IEnumerable <EntityRef> ids = Enumerable.Empty <EntityRef>( );

                    if (request.Aliases != null && request.Aliases.Length > 0)
                    {
                        ids = ids.Union(request.Aliases.Select(ConvertId));
                    }

                    if (request.Ids != null && request.Ids.Length > 0)
                    {
                        ids = ids.Union(request.Ids.Select(ConvertId));
                    }

                    IList <EntityRef> entityRefs = ids.ToList( );

                    if (entityRefs.All(er => er == null))
                    {
                        singleResult.Code = HttpStatusCode.NotFound;
                        continue;
                    }

                    // Run the request
                    IEnumerable <EntityData> entitiesData;
                    try
                    {
                        if (BulkRequestHelper.IsValidForBulkRequest(memberRequest))
                        {
                            var entityRequest = new EntityRequest
                            {
                                QueryType     = request.QueryType,
                                RequestString = memberRequest.RequestString,
                                Hint          = requestHintText,
                                Entities      = entityRefs,
                                Filter        = request.Filter,
                                Isolated      = request.Isolated
                            };

                            // Run the request
                            entitiesData = BulkRequestRunner.GetEntities(entityRequest);

                            singleResult.Cached = entityRequest.ResultFromCache;
                        }
                        else
                        {
                            EventLog.Application.WriteWarning(
                                "Request cannot be handled by BulkRequest. Hint: {0}\n{1}",
                                requestHintText, memberRequest.RequestString);

                            switch (request.QueryType)
                            {
                            case QueryType.Instances:
                                entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), true, memberRequest);
                                break;

                            case QueryType.ExactInstances:
                                entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), false, memberRequest);
                                break;

                            case QueryType.Basic:
                            case QueryType.BasicWithDemand:
                                entitiesData = svc.GetEntitiesData(entityRefs, memberRequest);
                                break;

                            default:
                                throw new ArgumentException(string.Format("Unknown query type {0}", request.QueryType));
                            }
                        }
                    }
                    catch (ArgumentException ex)
                    {
                        // sorry world!
                        if (ex.Message.Contains("does not represent a known entity"))
                        {
                            singleResult.Code = HttpStatusCode.BadRequest;
                            continue;
                        }
                        throw;
                    }

                    // Skip results where access is denied
                    foreach (EntityData entityData in entitiesData.Where(ed => ed != null))
                    {
                        AppendEntityToResult(result, entityData);
                        singleResult.Ids.Add(entityData.Id.Id);
                    }

                    // Set the result to NotFound for Basic and BasicWithDemand only
                    if (request.QueryType == QueryType.Basic || request.QueryType == QueryType.BasicWithDemand)
                    {
                        singleResult.Code = singleResult.Ids.Count > 0
                                                        ? HttpStatusCode.OK
                                                        : HttpStatusCode.NotFound;
                    }
                    else
                    {
                        singleResult.Code = HttpStatusCode.OK;
                    }
                }
                catch (PlatformSecurityException ex)
                {
                    EventLog.Application.WriteWarning(ex.ToString( ));
                    singleResult.Ids.Clear( );
                    singleResult.Code = HttpStatusCode.Forbidden;
                }
                catch (Exception ex)
                {
                    EventLog.Application.WriteError(ex.ToString( ));
                    singleResult.Code = HttpStatusCode.InternalServerError;
                }
                finally
                {
                    // Place in the finally block so we are certain that it gets run exactly once per iteration
                    result.Results.Add(singleResult);
                }
            }

            if (result.Results.Count != batch.Requests.Length)
            {
                // We are indexing by ID, so we must always ensure that request position matches result position.
                throw new Exception("Internal error: batch query mismatch.");
            }
        }
        /// <summary>
        ///     Gets the form data.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        public FormDataResponse GetFormData(FormDataRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var entityIdRef = new EntityRef(request.EntityId);

            // Get the entity data
            EntityData entityData = BulkRequestRunner.GetEntityData(entityIdRef, request.Query, request.Hint);

            // Set the result to NotFound for Basic and BasicWithDemand only
            if (entityData == null)
            {
                return(null);
            }

            var        formId         = new EntityRef(request.FormId).Id;
            EntityData formEntityData = null;

            // Get the form entity data for non gen forms
            if (!EntityTemporaryIdAllocator.IsAllocatedId(formId))
            {
                try
                {
                    formEntityData = GetFormAsEntityData(formId, false);
                }
                catch (Exception ex)
                {
                    EventLog.Application.WriteError("Failed to get form with id {0}. Unable to get initial form control visibility. Error: {1}.", formId, ex);
                }
            }

            ISet <long> initiallyHiddenControls = null;

            if (formEntityData == null)
            {
                return(PackageFormDataResponse(entityData, null));
            }

            IDictionary <long, IExpression> compiledExpressions = null;
            IDictionary <long, string>      controlVisibilityCalculations;

            using (new SecurityBypassContext())
            {
                // Have form. Get any visibility calculations
                controlVisibilityCalculations = GetControlVisibilityCalculations(formEntityData);

                long entityTypeId = GetTypeToEditWithForm(formEntityData);

                if (controlVisibilityCalculations.Count > 0 && entityTypeId > 0)
                {
                    // Now we have all the calculations
                    compiledExpressions = CompileVisibilityCalculations(new EntityRef(entityTypeId), controlVisibilityCalculations);
                }
            }

            if (controlVisibilityCalculations.Count > 0)
            {
                initiallyHiddenControls = GetHiddenControls(entityIdRef.Entity, controlVisibilityCalculations.Keys, compiledExpressions);
            }

            return(PackageFormDataResponse(entityData, initiallyHiddenControls));
        }
        /// <summary>
        ///     Gets the identity providers for the specified tenant.
        /// </summary>
        /// <returns>TenantIdentityProviderResponse.</returns>
        public TenantIdentityProviderResponse GetIdentityProviders(string tenant)
        {
            if (string.IsNullOrWhiteSpace(tenant))
            {
                throw new ArgumentNullException(nameof(tenant));
            }

            var response = new TenantIdentityProviderResponse {
                IdentityProviders = new List <IdentityProviderResponse>()
            };

            using (new SecurityBypassContext())
            {
                TenantAdministratorContext tenantAdminContext;

                try
                {
                    tenantAdminContext = new TenantAdministratorContext(tenant);
                }
                catch (EntityNotFoundException)
                {
                    return(response);
                }

                try
                {
                    var idProviderId = new EntityRef("core:identityProvider");
                    var isOfTypeId   = new EntityRef("core:isOfType");
                    var nameId       = new EntityRef("core:name");
                    var aliasId      = new EntityRef("core:alias");
                    var isEnabledId  = new EntityRef("core:isProviderEnabled");
                    var ordinalId    = new EntityRef("core:providerOrdinal");

                    // Get the identity providers for the given tenant
                    var entityRequest = new EntityRequest
                    {
                        Entities      = idProviderId.ToEnumerable(),
                        RequestString = "name, isProviderEnabled, providerOrdinal, isOfType.alias",
                        Hint          = "tenant ip providers",
                        QueryType     = QueryType.Instances
                    };

                    // Run the query
                    var instances = BulkRequestRunner.GetEntitiesByType(entityRequest);

                    if (instances == null)
                    {
                        return(response);
                    }

                    foreach (var instance in instances)
                    {
                        var    isEnabled = false;
                        string name      = null;
                        var    ordinal   = 0;
                        var    id        = instance.Id.Id;

                        foreach (var fieldData in instance.Fields)
                        {
                            if (fieldData.FieldId == null || fieldData.Value?.Type == null)
                            {
                                continue;
                            }

                            if (fieldData.FieldId.Id == nameId.Id)
                            {
                                name = fieldData.Value.ValueString;
                            }
                            else if (fieldData.FieldId.Id == ordinalId.Id &&
                                     fieldData.Value.Type.GetRunTimeType() == typeof(int) &&
                                     fieldData.Value.Value != null)
                            {
                                ordinal = (int)fieldData.Value.Value;
                            }
                            else if (fieldData.FieldId.Id == isEnabledId.Id &&
                                     fieldData.Value.Type.GetRunTimeType() == typeof(bool) &&
                                     fieldData.Value.Value != null)
                            {
                                isEnabled = (bool)fieldData.Value.Value;
                            }
                        }

                        if (!isEnabled || string.IsNullOrWhiteSpace(name))
                        {
                            continue;
                        }

                        var singleOrDefault =
                            instance.Relationships.SingleOrDefault(r => r.RelationshipTypeId.Id == isOfTypeId.Id);

                        if (singleOrDefault == null)
                        {
                            continue;
                        }

                        var isOfTypeEntityData = singleOrDefault.Entities;

                        var identityProvider = new IdentityProviderResponse
                        {
                            Id      = id,
                            Name    = name,
                            Ordinal = ordinal
                        };

                        if (isOfTypeEntityData != null)
                        {
                            var typeEntity = isOfTypeEntityData.FirstOrDefault();
                            if (typeEntity == null)
                            {
                                continue;
                            }

                            var typeAliasData = typeEntity.Fields.SingleOrDefault(f => f.FieldId.Id == aliasId.Id);

                            if (typeAliasData?.Value != null)
                            {
                                identityProvider.TypeAlias = typeAliasData.Value.ValueString;
                            }
                        }

                        response.IdentityProviders.Add(identityProvider);
                    }
                }
                finally
                {
                    tenantAdminContext.Dispose();
                }
            }

            return(response);
        }
Пример #15
0
        /// <summary>
        /// Sets the default reports for relationship controls.
        /// </summary>
        /// <param name="form">The form.</param>
        private static void SetDefaultReportsForRelationshipControls(EntityData form)
        {
            // Find the template report
            var relationshipDisplayReportRelId = Entity.GetId("console:relationshipDisplayReport");
            var pickerReportRelId         = Entity.GetId("console:pickerReport");
            var isReversedFieldId         = Entity.GetId("console:isReversed");
            var relationshipToRenderRelId = Entity.GetId("console:relationshipToRender");

            //  get all Inline and tabbed relationships
            var relationshipControls = new List <EntityData>();

            GetAllRelationshipControlsOnForm(form, relationshipControls);

            // list of reports that we need to load - mapped to relationship containers that we need to place them in
            var reportsToLoad = new List <Tuple <EntityRef, RelationshipData> >();

            foreach (EntityData relationshipControl in relationshipControls)
            {
                // get the relationship that this control represents
                var relationshipToRenderRelation = GetRelationDataByRelationId(relationshipToRenderRelId,
                                                                               relationshipControl.Relationships);

                if (relationshipToRenderRelation == null || relationshipToRenderRelation.Instances.Count <= 0)
                {
                    continue;
                }
                var relationshipId = relationshipToRenderRelation.Instances.First().Entity.Id;
                var relationship   = Entity.Get <Relationship>(relationshipId);

                // get the direction that we're following the relationship
                bool isReversed      = false;
                var  isReversedField = GetFieldDataByFieldId(isReversedFieldId, relationshipControl.Fields);
                if (isReversedField != null && isReversedField.Value.Value != null)
                {
                    bool.TryParse(isReversedField.Value.Value.ToString(), out isReversed);
                }

                // check display report
                CheckRelationshipReport(relationshipControl, relationshipDisplayReportRelId, relationship, isReversed, reportsToLoad, true);

                // check picker report
                CheckRelationshipReport(relationshipControl, pickerReportRelId, relationship, isReversed, reportsToLoad, false);
            }

            // load reports
            if (reportsToLoad.Count > 0)
            {
                var reportIds  = reportsToLoad.Select(r => r.Item1).Distinct();
                var reports    = BulkRequestRunner.GetEntitiesData(reportIds, CustomEditFormHelper.GetRelationshipReportQueryString, "Form report");
                var reportDict = reports.ToDictionarySafe(r => r.Id.Id);

                foreach (var task in reportsToLoad)
                {
                    EntityRef  reportId   = task.Item1;
                    EntityData reportData = reportDict[reportId.Id];
                    if (reportData != null)
                    {
                        // store data in the relationship container
                        RelationshipData container = task.Item2;
                        container.Instances.Add(
                            new RelationshipInstanceData()
                        {
                            DataState = DataState.Unchanged,
                            Entity    = reportData
                        });
                    }
                }
            }
        }
        /// <summary>
        /// Check whether the user has access to the entities by following relationships where the
        /// <see cref="Relationship"/> type has the <see cref="Relationship.SecuresTo"/> or
        /// <see cref="Relationship.SecuresFrom"/> flag set.
        /// </summary>
        /// <param name="permissions">
        /// The permissions to check for. This cannot be null or contain null.
        /// </param>
        /// <param name="user">
        /// The user to do the check access for. This cannot be null.
        /// </param>
        /// <param name="entitiesToCheck">
        /// The entities to check. This cannot be null or contain null.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        internal ISet <EntityRef> CheckAccessControlByRelationship(IList <EntityRef> permissions, EntityRef user,
                                                                   ISet <EntityRef> entitiesToCheck)
        {
            if (permissions == null)
            {
                throw new ArgumentNullException("permissions");
            }
            if (permissions.Contains(null))
            {
                throw new ArgumentNullException("permissions", "Cannot contain null");
            }
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
            if (entitiesToCheck == null)
            {
                throw new ArgumentNullException("entitiesToCheck");
            }

            EntityMemberRequest entityMemberRequest;
            IDictionary <long, ISet <EntityRef> > entityTypes;
            IEnumerable <EntityData> entitiesData;
            IDictionary <long, bool> accessToRelatedEntities;
            HashSet <EntityRef>      result;
            IList <long>             relatedAccessibleEntities;
            EntityType entityType;

            using (Profiler.MeasureAndSuppress("SecuresFlagEntityAccessControlChecker.CheckAccessControlByRelationship"))
            {
                result = new HashSet <EntityRef>(EntityRefComparer.Instance);

                entityTypes = TypeRepository.GetEntityTypes(entitiesToCheck);
                using (MessageContext outerMessageContext = new MessageContext(EntityAccessControlService.MessageName))
                    foreach (KeyValuePair <long, ISet <EntityRef> > entitiesType in entityTypes)
                    {
                        outerMessageContext.Append(() =>
                                                   string.Format(
                                                       "Checking relationships for entity(ies) \"{0}\" of type \"{1}\":",
                                                       string.Join(", ", entitiesType.Value.Select(er => string.Format("'{0}' ({1})", er.Entity.As <Resource>().Name, er.Id))),
                                                       string.Join(", ", string.Format("'{0}' ({1})", Entity.Get <Resource>(entitiesType.Key).Name, entitiesType.Key))));

                        using (MessageContext innerMessageContext = new MessageContext(EntityAccessControlService.MessageName))
                        {
                            entityType = Entity.Get <EntityType>(entitiesType.Key);
                            if (entityType != null)
                            {
                                entityMemberRequest = EntityMemberRequestFactory.BuildEntityMemberRequest(entityType, permissions);
                                if (entityMemberRequest.Relationships.Count > 0)
                                {
                                    IList <EntityRef> relatedEntitiesToCheck;

                                    innerMessageContext.Append(() => string.Format("Security relationship structure for entity type '{0}':", entityType.Id));
                                    TraceEntityMemberRequest(entityMemberRequest);

                                    // Get the IDs of entities to check security for
                                    EntityRequest request = new EntityRequest
                                    {
                                        Entities          = entitiesType.Value,
                                        Request           = entityMemberRequest,
                                        IgnoreResultCache = true    // security engine does its own result caching
                                    };
                                    entitiesData = BulkRequestRunner.GetEntitiesData(request).ToList( );

                                    // Do a single security check for all entities related to
                                    // the entities passed in, excluding the original entities
                                    // that failed the security check.
                                    relatedEntitiesToCheck = Delegates
                                                             .WalkGraph(
                                        entitiesData,
                                        entityData =>
                                        entityData.Relationships.SelectMany(relType => relType.Entities))
                                                             .Select(ed => ed.Id)
                                                             .Where(er => !entitiesType.Value.Contains(er, EntityRefComparer.Instance))
                                                             .ToList();
                                    if (relatedEntitiesToCheck.Count > 0)
                                    {
                                        // Add the relationship types to watch for cache invalidations
                                        using (CacheContext cacheContext = CacheContext.GetContext())
                                        {
                                            IList <long> relationshipTypes = Delegates
                                                                             .WalkGraph(entityMemberRequest.Relationships,
                                                                                        rr => rr.RequestedMembers.Relationships)
                                                                             .Select(rr => rr.RelationshipTypeId.Id)
                                                                             .ToList();

                                            cacheContext.RelationshipTypes.Add(relationshipTypes);
                                        }

                                        // ReSharper disable AccessToModifiedClosure
                                        // Do a single access check for all entities for efficiency, then pick the
                                        // important ones for each requested entity below.
                                        accessToRelatedEntities = null;
                                        innerMessageContext.Append(
                                            () => string.Format(
                                                "Checking related entities '{0}':",
                                                string.Join(", ", relatedEntitiesToCheck.Select(er => er.Id))));
                                        SecurityBypassContext.RunAsUser(
                                            () =>
                                            accessToRelatedEntities =
                                                Checker.CheckAccess(relatedEntitiesToCheck, permissions, user));
                                        // ReSharper restore AccessToModifiedClosure

                                        foreach (EntityData entityData in entitiesData)
                                        {
                                            // Get the related entities to check
                                            relatedEntitiesToCheck = Delegates.WalkGraph(
                                                entityData,
                                                ed => ed.Relationships.SelectMany(relType => relType.Entities))
                                                                     .Select(ed => ed.Id)
                                                                     .ToList();

                                            // Remove the start entity for the query, since security has
                                            // already been checked on it.
                                            relatedEntitiesToCheck.Remove(entityData.Id);

                                            // Get the related entities the user has access to
                                            relatedAccessibleEntities = accessToRelatedEntities
                                                                        .Where(kvp => kvp.Value && relatedEntitiesToCheck.Contains(kvp.Key, EntityRefComparer.Instance))
                                                                        .Select(kvp => kvp.Key)
                                                                        .ToList();

                                            // Grant access if the user has access to ANY of the related
                                            // entities.
                                            if (relatedEntitiesToCheck.Count > 0 &&
                                                relatedAccessibleEntities.Count > 0)
                                            {
                                                result.Add(entityData.Id);

                                                // ReSharper disable AccessToModifiedClosure
                                                innerMessageContext.Append(
                                                    () => string.Format(
                                                        "Access to '{0}' granted due to corresponding access to '{1}'",
                                                        string.Join(", ", relatedEntitiesToCheck.Select(id => string.Format("'{0}' ({1})", Entity.Get <Resource>(id).Name, id))),
                                                        string.Join(", ", relatedAccessibleEntities.Select(id => string.Format("'{0}' ({1})", Entity.Get <Resource>(id).Name, id)))));
                                                // ReSharper restore AccessToModifiedClosure
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    innerMessageContext.Append(() => string.Format("No relationships found to check for entity type '{0}'.", entityType.Id));
                                }
                            }
                            else
                            {
                                EventLog.Application.WriteWarning("Type ID {0} for entities '{1}' is not a type",
                                                                  entitiesType.Key, string.Join(", ", entitiesType.Value));
                            }
                        }
                    }
            }

            return(result);
        }