public static SagaMetadata Create(Type sagaType, IEnumerable <Type> availableTypes, Conventions conventions)
        {
            if (!IsSagaType(sagaType))
            {
                throw new Exception(sagaType.FullName + " is not a saga");
            }

            var genericArguments = GetBaseSagaType(sagaType).GetGenericArguments();

            if (genericArguments.Length != 1)
            {
                throw new Exception(string.Format("'{0}' saga type does not implement Saga<T>", sagaType));
            }

            var sagaEntityType           = genericArguments.Single();
            var uniquePropertiesOnEntity = FindUniqueAttributes(sagaEntityType).ToList();

            var mapper = new SagaMapper();

            var saga = (Saga)FormatterServices.GetUninitializedObject(sagaType);

            saga.ConfigureHowToFindSaga(mapper);

            ApplyScannedFinders(mapper, sagaEntityType, availableTypes, conventions);


            var finders = new List <SagaFinderDefinition>();

            foreach (var mapping in mapper.Mappings)
            {
                if (uniquePropertiesOnEntity.All(p => p.Name != mapping.SagaPropName))
                {
                    uniquePropertiesOnEntity.Add(new CorrelationProperty {
                        Name = mapping.SagaPropName
                    });
                }


                SetFinderForMessage(mapping, sagaEntityType, finders);
            }

            var associatedMessages = GetAssociatedMessages(sagaType)
                                     .ToList();

            var metadata = new SagaMetadata(associatedMessages, finders)
            {
                Name                  = sagaType.FullName,
                EntityName            = sagaEntityType.FullName,
                CorrelationProperties = uniquePropertiesOnEntity,
                SagaEntityType        = sagaEntityType,
                SagaType              = sagaType
            };

            return(metadata);
        }
        static void ApplyScannedFinders(SagaMapper mapper, Type sagaEntityType, IEnumerable <Type> availableTypes, Conventions conventions)
        {
            var actualFinders = availableTypes.Where(t => typeof(IFinder).IsAssignableFrom(t))
                                .ToList();

            foreach (var finderType in actualFinders)
            {
                foreach (var interfaceType in finderType.GetInterfaces())
                {
                    var args = interfaceType.GetGenericArguments();
                    if (args.Length != 2)
                    {
                        continue;
                    }

                    Type messageType = null;
                    Type entityType  = null;
                    foreach (var type in args)
                    {
                        if (typeof(IContainSagaData).IsAssignableFrom(type))
                        {
                            entityType = type;
                        }

                        if (conventions.IsMessageType(type) || type == typeof(object))
                        {
                            messageType = type;
                        }
                    }

                    if (entityType == null || messageType == null || entityType != sagaEntityType)
                    {
                        continue;
                    }

                    var existingMapping = mapper.Mappings.SingleOrDefault(m => m.MessageType == messageType);

                    if (existingMapping != null)
                    {
                        existingMapping.CustomFinderType = finderType;
                    }
                    else
                    {
                        mapper.ConfigureCustomFinder(finderType, messageType);
                    }
                }
            }
        }
Beispiel #3
0
        static void ApplyScannedFinders(SagaMapper mapper, Type sagaEntityType, IEnumerable <Type> availableTypes, Conventions conventions)
        {
            var actualFinders = availableTypes.Where(t => typeof(IFinder).IsAssignableFrom(t) && t.IsClass)
                                .ToList();

            foreach (var finderType in actualFinders)
            {
                foreach (var interfaceType in finderType.GetInterfaces())
                {
                    var args = interfaceType.GetGenericArguments();
                    //since we don't want to process the IFinder type
                    if (args.Length != 2)
                    {
                        continue;
                    }

                    var entityType = args[0];
                    if (entityType != sagaEntityType)
                    {
                        continue;
                    }

                    var messageType = args[1];
                    if (!conventions.IsMessageType(messageType))
                    {
                        var error = $"A custom IFindSagas must target a valid message type as defined by the message conventions. Change '{messageType.FullName}' to a valid message type or add it to the message conventions. Finder name '{finderType.FullName}'.";
                        throw new Exception(error);
                    }

                    var existingMapping = mapper.Mappings.SingleOrDefault(m => m.MessageType == messageType);
                    if (existingMapping != null)
                    {
                        var bothMappingAndFinder = $"A custom IFindSagas and an existing mapping where found for message '{messageType.FullName}'. Either remove the message mapping for remove the finder. Finder name '{finderType.FullName}'.";
                        throw new Exception(bothMappingAndFinder);
                    }
                    mapper.ConfigureCustomFinder(finderType, messageType);
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Creates a <see cref="SagaMetadata" /> from a specific Saga type.
        /// </summary>
        /// <param name="sagaType">A type representing a Saga. Must be a non-generic type inheriting from <see cref="Saga" />.</param>
        /// <param name="availableTypes">Additional available types, used to locate saga finders and other related classes.</param>
        /// <param name="conventions">Custom conventions to use while scanning types.</param>
        /// <returns>An instance of <see cref="SagaMetadata" /> describing the Saga.</returns>
        public static SagaMetadata Create(Type sagaType, IEnumerable <Type> availableTypes, Conventions conventions)
        {
            Guard.AgainstNull(nameof(sagaType), sagaType);
            Guard.AgainstNull(nameof(availableTypes), availableTypes);
            Guard.AgainstNull(nameof(conventions), conventions);
            if (!IsSagaType(sagaType))
            {
                throw new Exception(sagaType.FullName + " is not a saga");
            }

            var genericArguments = GetBaseSagaType(sagaType).GetGenericArguments();

            if (genericArguments.Length != 1)
            {
                throw new Exception($"'{sagaType.Name}' saga type does not implement Saga<T>");
            }

            var saga   = (Saga)FormatterServices.GetUninitializedObject(sagaType);
            var mapper = new SagaMapper();

            saga.ConfigureHowToFindSaga(mapper);

            var sagaEntityType = genericArguments.Single();

            ApplyScannedFinders(mapper, sagaEntityType, availableTypes, conventions);

            var finders = new List <SagaFinderDefinition>();


            var propertyMappings = mapper.Mappings.Where(m => !m.HasCustomFinderMap)
                                   .GroupBy(m => m.SagaPropName)
                                   .ToList();

            if (propertyMappings.Count > 1)
            {
                var messageTypes = string.Join(",", propertyMappings.SelectMany(g => g.Select(m => m.MessageType.FullName)).Distinct());
                throw new Exception($"Sagas can only have mappings that correlate on a single saga property. Use custom finders to correlate {messageTypes} to saga {sagaType.Name}");
            }

            CorrelationPropertyMetadata correlationProperty = null;

            if (propertyMappings.Any())
            {
                var mapping = propertyMappings.Single().First();
                correlationProperty = new CorrelationPropertyMetadata(mapping.SagaPropName, mapping.SagaPropType);
            }

            var associatedMessages = GetAssociatedMessages(sagaType)
                                     .ToList();

            foreach (var mapping in mapper.Mappings)
            {
                var associatedMessage = associatedMessages.FirstOrDefault(m => mapping.MessageType.IsAssignableFrom(m.MessageType));
                if (associatedMessage == null)
                {
                    var msgType = mapping.MessageType.Name;
                    if (mapping.HasCustomFinderMap)
                    {
                        throw new Exception($"Custom saga finder {mapping.CustomFinderType.FullName} maps message type {msgType} for saga {sagaType.Name}, but the saga does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>.");
                    }
                    throw new Exception($"Saga {sagaType.Name} contains a mapping for {msgType} in the ConfigureHowToFindSaga method, but does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>.");
                }
                SetFinderForMessage(mapping, sagaEntityType, finders);
            }

            foreach (var messageType in associatedMessages)
            {
                if (messageType.IsAllowedToStartSaga)
                {
                    var match = mapper.Mappings.FirstOrDefault(m => m.MessageType.IsAssignableFrom(messageType.MessageType));
                    if (match == null)
                    {
                        var simpleName = messageType.MessageType.Name;
                        throw new Exception($"Message type {simpleName} can start the saga {sagaType.Name} (the saga implements IAmStartedByMessages<{simpleName}>) but does not map that message to saga data. In the ConfigureHowToFindSaga method, add a mapping using:{Environment.NewLine}    mapper.ConfigureMapping<{simpleName}>(message => message.SomeMessageProperty).ToSaga(saga => saga.MatchingSagaProperty);");
                    }
                }
            }

            return(new SagaMetadata(sagaType.FullName, sagaType, sagaEntityType.FullName, sagaEntityType, correlationProperty, associatedMessages, finders));
        }