示例#1
0
    public void IterationSetup()
    {
        var eventSource   = new EventSourceId("46c4de33-9a60-4465-97ab-a2a7f5b7e6a3");
        var aggregateRoot = new Artifact(new ArtifactId(Guid.NewGuid()), ArtifactGeneration.First);

        _eventsToCommit = new UncommittedAggregateEvents(
            eventSource,
            aggregateRoot,
            AggregateRootVersion.Initial + (ulong)PreExistingEvents,
            _eventsToCommit);

        if (PreExistingEvents > 0)
        {
            var events = new List <UncommittedEvent>();
            for (var n = 0; n < PreExistingEvents; n++)
            {
                events.Add(new UncommittedEvent(
                               new EventSourceId("46c4de33-9a60-4465-97ab-a2a7f5b7e6a3"),
                               new Artifact(new ArtifactId(Guid.Parse("08db4b0a-3724-444f-9968-ada44922fb78")), ArtifactGeneration.First),
                               false,
                               "{ \"hello\": \"world\" }"));
            }
            var preExistingEvents = new UncommittedAggregateEvents(
                eventSource,
                aggregateRoot,
                AggregateRootVersion.Initial,
                events);

            _eventStore.Commit(preExistingEvents, _executionContext).GetAwaiter().GetResult();
        }
    }
示例#2
0
 public async Task CommitEventsInLoop()
 {
     for (var n = 0; n < EventsToCommit; n++)
     {
         await _eventStore.Commit(new UncommittedEvents(new[]
         {
             _eventsToCommit[n]
         }), _executionContext).ConfigureAwait(false);
     }
 }
示例#3
0
    /// <inheritdoc />
    protected override void Setup(IServiceProvider services)
    {
        _eventStore       = services.GetRequiredService <IEventStore>();
        _executionContext = Runtime.CreateExecutionContextFor(ConfiguredTenants.First());

        _aggregateRoot = new ArtifactId(Guid.NewGuid());
        _eventSource   = new EventSourceId("fe7d58f0-2f4c-4736-8f9e-68dd7dfaec82");

        if (CommittedEvents < 1)
        {
            return;
        }

        for (var n = 0; n < CommittedEvents; n++)
        {
            _eventStore.Commit(
                new UncommittedAggregateEvents(
                    _eventSource,
                    new Artifact(_aggregateRoot, ArtifactGeneration.First),
                    (uint)n,
                    new[]
            {
                new UncommittedEvent(
                    _eventSource,
                    new Artifact(new ArtifactId(Guid.Parse("f029f857-f9e6-45d4-b3ab-4b9b0d798f2c")), ArtifactGeneration.First),
                    false,
                    "{ \"hello\": \"world\" }")
            }),
                _executionContext).GetAwaiter().GetResult();

            if (UnrelatedEventsRatio < 1)
            {
                continue;
            }

            for (var m = 0; m < UnrelatedEventsRatio; m++)
            {
                _eventStore.Commit(
                    new UncommittedEvents(new[]
                {
                    new UncommittedEvent(
                        _eventSource,
                        new Artifact(new ArtifactId(Guid.Parse("9f90d6df-0711-4ac9-bf1c-51d6090733e9")), ArtifactGeneration.First),
                        false,
                        "{ \"hello\": \"world\" }")
                }),
                    _executionContext).GetAwaiter().GetResult();
            }
        }
    }
示例#4
0
    /// <inheritdoc />
    protected override void Setup(IServiceProvider services)
    {
        _eventStore       = services.GetRequiredService <IEventStore>();
        _executionContext = Runtime.CreateExecutionContextFor(ConfiguredTenants.First());

        var events = new List <UncommittedEvent>();

        for (var n = 0; n < EventsToCommit; n++)
        {
            events.Add(new UncommittedEvent(
                           new EventSourceId("8453f4d4-861a-4042-a4e3-c1abf1c8eadd"),
                           new Artifact(new ArtifactId(Guid.Parse("752f88c9-70f0-4ffe-82b6-a69dcc96672e")), ArtifactGeneration.First),
                           false,
                           "{ \"hello\": \"world\" }"));
        }
        _eventsToCommit = new UncommittedEvents(events);

        if (PreExistingEvents < 1)
        {
            return;
        }

        var preExistingEvents = new List <UncommittedEvent>();

        for (var n = 0; n < PreExistingEvents; n++)
        {
            preExistingEvents.Add(new UncommittedEvent(
                                      new EventSourceId("8453f4d4-861a-4042-a4e3-c1abf1c8eadd"),
                                      new Artifact(new ArtifactId(Guid.Parse("752f88c9-70f0-4ffe-82b6-a69dcc96672e")), ArtifactGeneration.First),
                                      false,
                                      "{ \"hello\": \"world\" }"));
        }

        _eventStore.Commit(new UncommittedEvents(preExistingEvents), _executionContext).GetAwaiter().GetResult();
    }
示例#5
0
        public ICommandHandlingResult Commit(ICommandHandlerContext context, TAggregateRoot aggregateRoot)
        {
            Contract.Assume(context?.Command != null);
            Contract.Assume(context.Metadata != null);
            Contract.Assume(aggregateRoot?.State != null);

            var command = context.Command;

            // get state tracker from aggregate root state
            var stateTracker = aggregateRoot.State.ExternalStateTracker as IEventProviderStateTracker;

            Contract.Assume(stateTracker?.Revisions != null);

            // make new transaction identity
            TransactionIdentity transactionIdentity = CreateNewTransactionIdentity();

            // create new transaction
            var transaction = new EventProviderTransaction(transactionIdentity, stateTracker.EventProvider, command, aggregateRoot, stateTracker.Revisions, context.Metadata);

            // store transaction
            _eventStore.Commit(transaction);

            // commit state tracker
            stateTracker.Commit();

            // return result
            return(CreateCommandHandlingResult(command, aggregateRoot, transactionIdentity));
        }
示例#6
0
#pragma warning disable 1591 // Xml Comments
        public void Commit(UncommittedEventStream uncommittedEventStream)
        {
            var committedEventStream = _eventStore.Commit(uncommittedEventStream);

            _eventSubscriptionManager.Process(committedEventStream);
            _eventStoreChangeManager.NotifyChanges(_eventStore, committedEventStream);
        }
        /// <inheritdoc/>
        public void Commit(TransactionCorrelationId correlationId, UncommittedEventStream uncommittedEventStream)
        {
            _logger.Information($"Committing uncommitted event stream with correlationId '{correlationId}'");
            var envelopes        = _eventEnvelopes.CreateFrom(uncommittedEventStream.EventSource, uncommittedEventStream.EventsAndVersion);
            var envelopesAsArray = envelopes.ToArray();
            var eventsAsArray    = uncommittedEventStream.ToArray();

            _logger.Trace("Create an array of events and envelopes");
            var eventsAndEnvelopes = new List <EventAndEnvelope>();

            for (var eventIndex = 0; eventIndex < eventsAsArray.Length; eventIndex++)
            {
                var envelope = envelopesAsArray[eventIndex];
                var @event   = eventsAsArray[eventIndex];
                eventsAndEnvelopes.Add(new EventAndEnvelope(
                                           envelope
                                           .WithTransactionCorrelationId(correlationId)
                                           .WithSequenceNumber(_eventSequenceNumbers.Next())
                                           .WithSequenceNumberForEventType(_eventSequenceNumbers.NextForType(envelope.Event)),
                                           @event
                                           ));
            }

            _logger.Trace("Committing events to event store");
            _eventStore.Commit(eventsAndEnvelopes);

            _logger.Trace($"Set event source versions for the event source '{envelopesAsArray[0].EventSource}' with id '{envelopesAsArray[0].EventSourceId}'");
            _eventSourceVersions.SetFor(envelopesAsArray[0].EventSource, envelopesAsArray[0].EventSourceId, envelopesAsArray[envelopesAsArray.Length - 1].Version);

            _logger.Trace("Create a committed event stream");
            var committedEventStream = new CommittedEventStream(uncommittedEventStream.EventSourceId, eventsAndEnvelopes);

            _logger.Trace("Send the committed event stream");
            _committedEventStreamSender.Send(committedEventStream);
        }
示例#8
0
        /// <inheritdoc/>
        public void Commit(TransactionCorrelationId correlationId, UncommittedEventStream uncommittedEventStream)
        {
            var envelopes        = _eventEnvelopes.CreateFrom(uncommittedEventStream.EventSource, uncommittedEventStream.EventsAndVersion);
            var envelopesAsArray = envelopes.ToArray();
            var eventsAsArray    = uncommittedEventStream.ToArray();

            var eventsAndEnvelopes = new List <EventAndEnvelope>();

            for (var eventIndex = 0; eventIndex < eventsAsArray.Length; eventIndex++)
            {
                var envelope = envelopesAsArray[eventIndex];
                var @event   = eventsAsArray[eventIndex];
                eventsAndEnvelopes.Add(new EventAndEnvelope(
                                           envelope
                                           .WithTransactionCorrelationId(correlationId)
                                           .WithSequenceNumber(_eventSequenceNumbers.Next())
                                           .WithSequenceNumberForEventType(_eventSequenceNumbers.NextForType(envelope.Event)),
                                           @event
                                           ));
            }

            _eventStore.Commit(eventsAndEnvelopes);
            _eventSourceVersions.SetFor(envelopesAsArray[0].EventSource, envelopesAsArray[0].EventSourceId, envelopesAsArray[envelopesAsArray.Length - 1].Version);

            var committedEventStream = new CommittedEventStream(uncommittedEventStream.EventSourceId, eventsAndEnvelopes);

            _committedEventStreamSender.Send(committedEventStream);
        }
示例#9
0
        public Response Apply(TransferRequest req)
        {
            var srcAccount  = Get(req.SourceAccountId);
            var destAccount = Get(req.DestinationAccountId);
            var srcEvent    = req.ToSourceAccountTransaction();
            var destEvent   = req.ToDestinationAccountTransaction();

            var srcEventValid  = srcAccount.ValidateProposed(srcEvent);
            var destEventValid = destAccount.ValidateProposed(destEvent);

            if (!srcEventValid || !destEventValid)
            {
                return(Response.Errored(ResponseStatus.InvalidState, $"{srcEventValid.IssuesMessage} {destEventValid.IssuesMessage}"));
            }

            _eventStore.Commit(srcEvent, destEvent);
            return(Response.Success());
        }
示例#10
0
 public Response Apply(AssignTask req)
 {
     if (!_tasks.Exists(req.TaskId))
     {
         return(Response.Errored(ResponseStatus.UnknownEntity, $"Unknown Task {req.TaskId}"));
     }
     return(_users
            .IfExists(req.UserId)
            .And(req.StartsAt.IsNotPast())
            .Then(() => _eventStore.Commit(req.ToEvent())));
 }
示例#11
0
		public EventStoreBaseSpec()
		{
			this.store = (IEventStore<Guid, DomainEvent>)CreateStore();

			// Override the ambient singleton value for tests.
			SystemClock.Instance = this.clock;

			var product = new Product(id1, "DevStore");
			product.Publish(1);
			product.Publish(2);
			product.Publish(3);
			store.Persist(product);
			store.Commit();

			product = new Product(id2, "WoVS");
			product.Publish(1);
			product.Publish(2);

			store.Persist(product);
			store.Commit();
		}
示例#12
0
        public EventStoreBaseSpec()
        {
            this.store = (IEventStore <Guid, DomainEvent>)CreateStore();

            // Override the ambient singleton value for tests.
            SystemClock.Instance = this.clock;

            var product = new Product(id1, "DevStore");

            product.Publish(1);
            product.Publish(2);
            product.Publish(3);
            store.Persist(product);
            store.Commit();

            product = new Product(id2, "WoVS");
            product.Publish(1);
            product.Publish(2);

            store.Persist(product);
            store.Commit();
        }
示例#13
0
        public void SaveUncommittedEventsToEventStore(IEventStore eventStore)
        {
            foreach (var aggregatedRootId in _aggregatedRootEvents.Keys)
            {
                var events = _aggregatedRootEvents[aggregatedRootId];

                var uncommittedEventStream = new UncommittedEventStream(aggregatedRootId);
                foreach (var @event in events)
                {
                    uncommittedEventStream.Append(@event);
                }

                eventStore.Commit(uncommittedEventStream);
            }
        }
        /// <inheritdoc/>
        public async Task HandlePostRequest(HttpContext context, IEvent @event)
        {
            var eventSourceQuery = context.Request.Query["EventSource-ID"].ToString();

            if (Guid.TryParse(eventSourceQuery, out var eventSourceId))
            {
                var uncommittedEvents = new UncommittedEvents();
                uncommittedEvents.Append(eventSourceId, @event);
                var committedEvents = await _eventStore.Commit(uncommittedEvents).ConfigureAwait(false);

                await context.RespondWithOk($"Event {@event.GetType()} committed. \nCommittedEvents: {committedEvents}").ConfigureAwait(false);
            }
            else
            {
                await context.RespondWithBadRequest($"A valid GUID is required as the EventSource-ID parameter. Received {eventSourceQuery}").ConfigureAwait(false);
            }
        }
示例#15
0
    /// <inheritdoc />
    protected override void Setup(IServiceProvider services)
    {
        _eventStore  = services.GetRequiredService <IEventStore>();
        _projections = services.GetRequiredService <IProjections>();
        _eventTypes  = Enumerable.Range(0, EventTypes).Select(_ => new ArtifactId(Guid.NewGuid())).ToArray();
        var uncommittedEvents = new List <UncommittedEvent>();

        foreach (var eventType in _eventTypes)
        {
            foreach (var _ in Enumerable.Range(0, Events))
            {
                uncommittedEvents.Add(new UncommittedEvent("some event source", new Artifact(eventType, ArtifactGeneration.First), false, "{\"hello\": 42}"));
            }
        }
        foreach (var tenant in ConfiguredTenants)
        {
            _eventStore.Commit(new UncommittedEvents(uncommittedEvents), Runtime.CreateExecutionContextFor(tenant)).GetAwaiter().GetResult();
        }
    }
示例#16
0
        private async Task SaveAggregateChangesAsync(ICorrelationContext context, AggregateRoot aggregate)
        {
            var uncommittedEvents = aggregate.UncommittedEvents
                                    .OrderBy(o => o.CreatedAt)
                                    .Cast <UncommittedEvent>()
                                    .Select(GenerateMetadata)
                                    .ToList();

            await _eventStore.AppendAsync(aggregate, uncommittedEvents);

            foreach (var @event in uncommittedEvents)
            {
                await _busClient.PublishAsync(@event.Data, context);
            }
            await _eventStore.Commit(aggregate);

            //ToDo remove events from store if failed
            aggregate.UpdateVersion(aggregate.Sequence);
            aggregate.ClearUncommittedEvents();
        }
示例#17
0
 public static Response Commit(this IEventStore events, params Event[] e)
 {
     events.Commit(e);
     return(Response.Success());
 }
示例#18
0
 public Response Set(SetPledge req)
 {
     return(_users.IfExists(req.UserId)
            .And(req.StartsAt.IsNotPast())
            .Then(() => _eventStore.Commit(req.ToEvent())));
 }
        public BoundedContextListener(
            KafkaConnectionString connectionString,
            ListenerConfiguration configuration,
            IEventConverter eventConverter,
            IUncommittedEventStreamCoordinator uncommittedEventStreamCoordinator,
            ISerializer serializer,
            ILogger logger,
            IApplicationResourceIdentifierConverter applicationResourceIdentifierConverter,
            IImplementationsOf <IEvent> eventTypes,
            IEventStore eventStore,
            IEventEnvelopes eventEnvelopes,
            IEventSequenceNumbers eventSequenceNumbers,
            IEventSourceVersions eventSourceVersions,
            ICommittedEventStreamBridge committedEventStreamBridge)
        {
            _serializer     = serializer;
            _eventConverter = eventConverter;
            _uncommittedEventStreamCoordinator = uncommittedEventStreamCoordinator;
            _logger = logger;
            _applicationResourceIdentifierConverter = applicationResourceIdentifierConverter;
            _eventTypes                 = eventTypes;
            _eventSequenceNumbers       = eventSequenceNumbers;
            _eventStore                 = eventStore;
            _eventEnvelopes             = eventEnvelopes;
            _eventSourceVersions        = eventSourceVersions;
            _committedEventStreamBridge = committedEventStreamBridge;



            logger.Information($"Listening on topic '{configuration.Topic}' from '{connectionString}'");

            var config = new Dictionary <string, object>
            {
                { "bootstrap.servers", connectionString },
                { "group.id", "simple-consumer" },
                { "enable.auto.commit", true },
                { "auto.commit.interval.ms", 1000 },
                {
                    "default.topic.config",
                    new Dictionary <string, object>()
                    {
                        { "auto.offset.reset", "smallest" }
                    }
                }
            };

            _consumer = new Consumer <Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8));
            _consumer.Assign(new [] { new TopicPartition(configuration.Topic, 0) });
            _consumer.OnMessage += (_, msg) =>
            {
                try
                {
                    logger.Trace($"Message received '{msg.Value}'");
                    var raw = _serializer.FromJson <dynamic[]>(msg.Value);

                    foreach (var rawContentAndEnvelope in raw)
                    {
                        var eventSourceId   = (EventSourceId)Guid.Parse(rawContentAndEnvelope.Content.EventSourceId.ToString());
                        var eventIdentifier = _applicationResourceIdentifierConverter.FromString(rawContentAndEnvelope.Envelope.Event.ToString());
                        var version         = EventSourceVersion.FromCombined((double)rawContentAndEnvelope.Envelope.Version);
                        var correlationId   = (TransactionCorrelationId)Guid.Parse(rawContentAndEnvelope.Envelope.CorrelationId.ToString());
                        CorrelationId = correlationId;

                        _logger.Trace($"Received event of with resource name '{eventIdentifier.Resource.Name}' from '{eventSourceId}' with version '{version}' in correlation '{correlationId}'");
                        var eventType = _eventTypes.SingleOrDefault(et => et.Name == eventIdentifier.Resource.Name);
                        if (eventType != null)
                        {
                            _logger.Trace("Matching Event Type : " + eventType.AssemblyQualifiedName);
                            var @event = Activator.CreateInstance(eventType, eventSourceId) as IEvent;
                            _serializer.FromJson(@event, rawContentAndEnvelope.Content.ToString());

                            var eventSource            = new ExternalSource(eventSourceId);
                            var uncommittedEventStream = new UncommittedEventStream(eventSource);
                            uncommittedEventStream.Append(@event, version);


                            _logger.Information($"Committing uncommitted event stream with correlationId '{correlationId}'");
                            var envelopes        = _eventEnvelopes.CreateFrom(eventSource, uncommittedEventStream.EventsAndVersion);
                            var envelopesAsArray = envelopes.ToArray();
                            var eventsAsArray    = uncommittedEventStream.ToArray();

                            _logger.Trace("Create an array of events and envelopes");
                            var eventsAndEnvelopes = new List <EventAndEnvelope>();
                            for (var eventIndex = 0; eventIndex < eventsAsArray.Length; eventIndex++)
                            {
                                var envelope     = envelopesAsArray[eventIndex];
                                var currentEvent = eventsAsArray[eventIndex];
                                eventsAndEnvelopes.Add(new EventAndEnvelope(
                                                           envelope
                                                           .WithTransactionCorrelationId(correlationId)
                                                           .WithSequenceNumber(_eventSequenceNumbers.Next())
                                                           .WithSequenceNumberForEventType(_eventSequenceNumbers.NextForType(envelope.Event)),
                                                           currentEvent
                                                           ));
                            }

                            _logger.Trace("Committing events to event store");
                            _eventStore.Commit(eventsAndEnvelopes);

                            _logger.Trace($"Set event source versions for the event source '{envelopesAsArray[0].EventSource}' with id '{envelopesAsArray[0].EventSourceId}'");
                            _eventSourceVersions.SetFor(envelopesAsArray[0].EventSource, envelopesAsArray[0].EventSourceId, envelopesAsArray[envelopesAsArray.Length - 1].Version);

                            _logger.Trace("Create a committed event stream");
                            var committedEventStream = new CommittedEventStream(uncommittedEventStream.EventSourceId, eventsAndEnvelopes);
                            _committedEventStreamBridge.Send(committedEventStream);

                            CorrelationId = Guid.Empty;
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, "Error during receiving");
                }
            };
        }
示例#20
0
        void Received(Topic topic, string eventAsJson)
        {
            try
            {
                _logger.Trace($"Message received 'eventAsJson'");
                dynamic raw = JsonConvert.DeserializeObject(eventAsJson);

                foreach (var rawContentAndEnvelope in raw)
                {
                    var eventSourceId   = (EventSourceId)Guid.Parse(rawContentAndEnvelope.Envelope.EventSourceId.ToString());
                    var eventIdentifier = _applicationResourceIdentifierConverter.FromString(rawContentAndEnvelope.Envelope.Event.ToString());
                    var version         = EventSourceVersion.FromCombined((double)rawContentAndEnvelope.Envelope.Version);
                    var correlationId   = (TransactionCorrelationId)Guid.Parse(rawContentAndEnvelope.Envelope.CorrelationId.ToString());
                    var eventSource     = new ExternalSource(eventSourceId);
                    CorrelationId = correlationId;

                    _logger.Trace($"Received event of with resource name '{eventIdentifier.Resource.Name}' from '{eventSourceId}' with version '{version}' in correlation '{correlationId}'");
                    var eventType = _eventTypes.SingleOrDefault(et => et.Name == eventIdentifier.Resource.Name);
                    if (eventType != null)
                    {
                        _logger.Trace("Matching Event Type : " + eventType.AssemblyQualifiedName);
                        var @event = GetEventFrom(rawContentAndEnvelope, eventSourceId, eventType);

                        var uncommittedEventStream = new UncommittedEventStream(eventSource);
                        uncommittedEventStream.Append(@event, version);


                        _logger.Information($"Committing uncommitted event stream with correlationId '{correlationId}'");
                        var envelopes = _eventEnvelopes.CreateFrom(eventSource, uncommittedEventStream.EventsAndVersion);

                        var envelopesAsArray = envelopes.ToArray();
                        var eventsAsArray    = uncommittedEventStream.ToArray();

                        _logger.Trace("Create an array of events and envelopes");
                        var eventsAndEnvelopes = new List <EventAndEnvelope>();
                        for (var eventIndex = 0; eventIndex < eventsAsArray.Length; eventIndex++)
                        {
                            var envelope     = envelopesAsArray[eventIndex];
                            var currentEvent = eventsAsArray[eventIndex];
                            eventsAndEnvelopes.Add(new EventAndEnvelope(
                                                       envelope
                                                       .WithTransactionCorrelationId(correlationId)
                                                       .WithSequenceNumber(_eventSequenceNumbers.Next())
                                                       .WithSequenceNumberForEventType(_eventSequenceNumbers.NextForType(envelope.Event)),
                                                       currentEvent
                                                       ));
                        }

                        _logger.Trace("Committing events to event store");
                        _eventStore.Commit(eventsAndEnvelopes);

                        _logger.Trace($"Set event source versions for the event source '{envelopesAsArray[0].EventSource}' with id '{envelopesAsArray[0].EventSourceId}'");
                        _eventSourceVersions.SetFor(envelopesAsArray[0].EventSource, envelopesAsArray[0].EventSourceId, envelopesAsArray[envelopesAsArray.Length - 1].Version);

                        _logger.Trace("Create a committed event stream");
                        var committedEventStream = new CommittedEventStream(uncommittedEventStream.EventSourceId, eventsAndEnvelopes);
                        _committedEventStreamBridge.Send(committedEventStream);

                        CorrelationId = Guid.Empty;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Error during receiving");
            }
        }
示例#21
0
        public void WhenFilteringByDateSince_ThenSucceeds()
        {
            var product = new Product(store.Query().For <Product>(id2).Execute());

            var when = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(5)).ToUniversalTime();

            this.clock.Now = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(7)).ToUniversalTime();
            product.Deactivate();
            store.Persist(product);
            store.Commit();

            this.clock.Now = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(6)).ToUniversalTime();
            product.Deactivate();
            store.Persist(product);
            store.Commit();

            this.clock.Now = when;
            product.Deactivate();
            store.Persist(product);
            store.Commit();

            this.clock.Now = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(4)).ToUniversalTime();
            product.Deactivate();
            store.Persist(product);
            store.Commit();

            var events = store.Query().OfType <Product.DeactivatedEvent>().Since(when).Execute();

            Assert.Equal(2, events.Count());
        }