/// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The instance of this object that will is the left side of the operator.</param>
        /// <param name="b">The EntityPathComponent instance that is the right side of the operator.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static EntityPath operator +(EntityPath a, EntityPathComponent b)
        {
            EntityPath path = a.Clone();

            path.Add(b);

            return(path);
        }
        /// <summary>
        /// Create a duplicate copy of this entity path and return it.
        /// </summary>
        /// <returns>A duplicate of this entity path.</returns>
        public EntityPath Clone()
        {
            EntityPath path = new EntityPath();

            path.AddRange(this);

            return(path);
        }
 /// <summary>
 /// Initialize a new queued entity for the given access path.
 /// </summary>
 /// <param name="entity">The entity that is going to be placed in the queue.</param>
 /// <param name="path">The initial path used to reach this entity.</param>
 public QueuedEntity(IEntity entity, EntityPath path)
 {
     Entity         = entity;
     ReferencePaths = new List <EntityPath>
     {
         path
     };
     ReferencedEntities = new List <KeyValuePair <string, IEntity> >();
     UserReferences     = new Dictionary <string, Reference>();
 }
        /// <summary>
        /// This is used for debug output to display the entity information and the path(s)
        /// that we took to find it.
        /// </summary>
        /// <returns>A string that describes this queued entity.</returns>
        public override string ToString()
        {
            StringBuilder sb          = new StringBuilder();
            EntityPath    primaryPath = ReferencePaths[0];

            sb.AppendFormat("{0} {1}", Entity.TypeName, Entity.Guid);
            foreach (var p in ReferencePaths)
            {
                sb.AppendFormat("\n\tPath");
                foreach (var e in p)
                {
                    sb.AppendFormat("\n\t\t{0} {2} {1}", e.Entity.TypeName, e.PropertyName, e.Entity.Guid);
                }
            }

            return(sb.ToString());
        }
 /// <summary>
 /// Add a new entity path reference to this existing entity.
 /// </summary>
 /// <param name="path">The path that can be used to reach this entity.</param>
 public void AddReferencePath(EntityPath path)
 {
     ReferencePaths.Add(path);
 }
        /// <summary>
        /// Generate the list of entities that reference this parent entity. These are entities that
        /// must be created after this entity has been created.
        /// </summary>
        /// <param name="parentEntity">The parent entity to find reverse-references to.</param>
        /// <param name="path">The property path that led us to this final property.</param>
        /// <param name="exporter">The object that handles filtering during an export process.</param>
        /// <returns>A list of KeyValuePairs that identify the property names and entites to be followed.</returns>
        protected List <KeyValuePair <string, IEntity> > FindChildEntities(IEntity parentEntity, EntityPath path, IExporter exporter)
        {
            List <KeyValuePair <string, IEntity> > children = new List <KeyValuePair <string, IEntity> >();

            var properties = GetEntityProperties(parentEntity);

            //
            // Take a stab at any properties that are an ICollection<IEntity> and treat those
            // as child entities.
            //
            foreach (var property in properties)
            {
                if (property.PropertyType.GetInterface("IEnumerable") != null && property.PropertyType.GetGenericArguments().Length == 1)
                {
                    if (typeof(IEntity).IsAssignableFrom(property.PropertyType.GetGenericArguments()[0]) && exporter.ShouldFollowPathProperty(path + new EntityPathComponent(parentEntity, property.Name)))
                    {
                        IEnumerable childEntities = ( IEnumerable )property.GetValue(parentEntity);

                        foreach (IEntity childEntity in childEntities)
                        {
                            children.Add(new KeyValuePair <string, IEntity>(property.Name, childEntity));
                        }
                    }
                }
            }

            //
            // We also need to pull in any attribute values. We have to pull attributes as well
            // since we might not have an actual value for that attribute yet and would need
            // it to pull the default value and definition.
            //
            if (parentEntity is IHasAttributes attributedEntity)
            {
                if (attributedEntity.Attributes == null)
                {
                    attributedEntity.LoadAttributes(RockContext);
                }

                foreach (var item in attributedEntity.Attributes)
                {
                    var attrib = new AttributeService(RockContext).Get(item.Value.Guid);

                    children.Add(new KeyValuePair <string, IEntity>("Attributes", attrib));

                    var value = new AttributeValueService(RockContext).Queryable()
                                .Where(v => v.AttributeId == attrib.Id && v.EntityId == attributedEntity.Id)
                                .FirstOrDefault();
                    if (value != null)
                    {
                        children.Add(new KeyValuePair <string, IEntity>("AttributeValues", value));
                    }
                }
            }

            //
            // Allow for processors to adjust the list of children.
            //
            foreach (var processor in FindEntityProcessors(GetEntityType(parentEntity)))
            {
                processor.EvaluateChildEntities(parentEntity, children, this);
            }

            return(children);
        }
        /// <summary>
        /// Find entities that this object references directly. These are entities that must be
        /// created before this entity can be re-created.
        /// </summary>
        /// <param name="parentEntity">The parent entity whose references we need to find.</param>
        /// <param name="path">The property path that led us to this final property.</param>
        /// <param name="exporter">The object that handles filtering during an export process.</param>
        /// <returns>A dictionary that identify the property names and entites to be followed.</returns>
        protected List <KeyValuePair <string, IEntity> > FindReferencedEntities(IEntity parentEntity, EntityPath path, IExporter exporter)
        {
            var references = new List <KeyValuePair <string, IEntity> >();
            var properties = GetEntityProperties(parentEntity);

            //
            // Take a stab at any properties that end in "Id" and likely reference another
            // entity, such as a property called "WorkflowId" probably references the Workflow
            // entity and should be linked by Guid.
            //
            foreach (var property in properties)
            {
                if (property.Name.EndsWith("Id") && (property.PropertyType == typeof(int) || property.PropertyType == typeof(Nullable <int>)))
                {
                    var entityProperty = parentEntity.GetType().GetProperty(property.Name.Substring(0, property.Name.Length - 2));

                    if (entityProperty == null || !typeof(IEntity).IsAssignableFrom(entityProperty.PropertyType))
                    {
                        continue;
                    }

                    var value = ( IEntity )entityProperty.GetValue(parentEntity);

                    if (exporter.ShouldFollowPathProperty(path + new EntityPathComponent(parentEntity, property.Name)))
                    {
                        references.Add(new KeyValuePair <string, IEntity>(property.Name, value));
                    }
                    else
                    {
                        references.Add(new KeyValuePair <string, IEntity>(property.Name, null));
                    }
                }
            }

            //
            // Allow for processors to adjust the list of children.
            //
            foreach (var processor in FindEntityProcessors(GetEntityType(parentEntity)))
            {
                processor.EvaluateReferencedEntities(parentEntity, references, this);
            }

            return(references);
        }
        /// <summary>
        /// Adds an entity to the queue list. This provides circular reference checking as
        /// well as ensuring that proper order is maintained for all entities.
        /// </summary>
        /// <param name="entity">The entity that is to be included in the export.</param>
        /// <param name="path">The entity path that lead to this entity being encoded.</param>
        /// <param name="entityIsCritical">True if the entity is critical, that is referenced directly.</param>
        /// <param name="exporter">The exporter that will handle processing for this entity.</param>
        protected void EnqueueEntity(IEntity entity, EntityPath path, bool entityIsCritical, IExporter exporter)
        {
            //
            // These are system generated rows, we should never try to backup or restore them.
            //
            if (entity.TypeName == "Rock.Model.EntityType" || entity.TypeName == "Rock.Model.FieldType")
            {
                return;
            }

            //
            // If the entity is already in our path that means we are beginning a circular
            // reference so we can just ignore this one.
            //
            if (path.Where(e => e.Entity.Guid == entity.Guid).Any())
            {
                return;
            }

            //
            // Find the entities that this entity references, in other words entities that must
            // exist before this one can be created, and queue them up.
            //
            var referencedEntities = FindReferencedEntities(entity, path, exporter);

            foreach (var r in referencedEntities)
            {
                if (r.Value != null)
                {
                    var refPath = path + new EntityPathComponent(entity, r.Key);
                    EnqueueEntity(r.Value, refPath, true, exporter);
                }
            }

            //
            // If we already know about the entity, add a reference to it and return.
            //
            var queuedEntity = Entities.Where(e => e.Entity.Guid == entity.Guid).FirstOrDefault();

            if (queuedEntity == null)
            {
                queuedEntity = new QueuedEntity(entity, path.Clone());
                Entities.Add(queuedEntity);
            }
            else
            {
                //
                // We have already visited this entity from the same parent. Not sure why we are here.
                //
                if (path.Any() && queuedEntity.ReferencePaths.Where(r => r.Any() && r.Last().Entity.Guid == path.Last().Entity.Guid).Any())
                {
                    return;
                }

                queuedEntity.AddReferencePath(path.Clone());
            }

            //
            // Add any new referenced properties/entities that may have been supplied for this
            // entity, as it's possible that has changed based on the path we took to get here.
            //
            foreach (var r in referencedEntities)
            {
                if (!queuedEntity.ReferencedEntities.Any(e => e.Key == r.Key && e.Value == r.Value))
                {
                    queuedEntity.ReferencedEntities.Add(new KeyValuePair <string, IEntity>(r.Key, r.Value));
                }
            }

            //
            // Mark the entity as critical if it's a root entity or is otherwise specified as critical.
            //
            if (path.Count == 0 || entityIsCritical || exporter.IsPathCritical(path))
            {
                queuedEntity.IsCritical = true;
            }

            //
            // Mark the entity as requiring a new Guid value if so indicated.
            //
            if (exporter.DoesPathNeedNewGuid(path))
            {
                queuedEntity.RequiresNewGuid = true;
            }

            //
            // Find the entities that this entity has as children. This is usually the many side
            // of a one-to-many reference (such as a Workflow has many WorkflowActions, this would
            // get a list of the WorkflowActions).
            //
            var children = FindChildEntities(entity, path, exporter);

            children.ForEach(e => EnqueueEntity(e.Value, path + new EntityPathComponent(entity, e.Key), false, exporter));

            //
            // Allow the exporter a chance to add custom reference values.
            //
            var userReferences = exporter.GetUserReferencesForPath(entity, path);

            if (userReferences != null && userReferences.Any())
            {
                foreach (var r in userReferences)
                {
                    queuedEntity.UserReferences.AddOrReplace(r.Property, r);
                }
            }
        }