示例#1
0
        private IEventStream PrepareStream(IAggregate aggregate, Dictionary <string, object> headers)
        {
            IEventStream stream;

            if (!this.streams.TryGetValue(aggregate.Id, out stream))
            {
                this.streams[aggregate.Id] = stream = this.eventStore.CreateStream(aggregate.Id);
            }

            foreach (var item in headers)
            {
                stream.UncommittedHeaders[item.Key] = item.Value;
            }

            aggregate.GetUncommittedEvents()
            .Cast <object>()
            .Select(x => new EventMessage {
                Body = x
            })
            .ToList()
            .ForEach(stream.Add);

            aggregate.ClearUncommittedEvents();

            return(stream);
        }
示例#2
0
        public void Save(IAggregate aggregate)
        {
            var commitId = Guid.NewGuid();
            var events   = aggregate.GetUncommittedEvents().ToArray();

            if (events.Any() == false)
            {
                return;
            }
            var streamName      = GetStreamName(aggregate.GetType(), aggregate.Id);
            var originalVersion = aggregate.Version - events.Count();
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var commitHeaders   = new Dictionary <string, object>
            {
                { "CommitId", commitId },
                { "AggregateClrType", aggregate.GetType().AssemblyQualifiedName }
            };
            var eventsToSave = events.Select(e => e.ToEventData(commitHeaders)).ToList();

            _connection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave).Wait();
            aggregate.ClearUncommittedEvents();

            foreach (var evento in events)
            {
                if (evento.GetType().ToString().Equals("CantidadIncrementada"))
                {
                    new HandlerCantidadIncrementada().Hanlder((CantidadIncrementada)evento);
                }
                else if (evento.GetType().ToString().Equals("CantidadDecrementada"))
                {
                    new HandlerCantidadDecrementada().Hanlder((CantidadDecrementada)evento);
                }
            }
        }
        public void Save(string bucketId, IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            Dictionary <string, object> headers = PrepareHeaders(aggregate, updateHeaders);

            while (true)
            {
                IEventStream stream           = PrepareStream(bucketId, aggregate, headers);
                int          commitEventCount = stream.CommittedEvents.Count;

                try {
                    stream.CommitChanges(commitId);
                    aggregate.ClearUncommittedEvents();
                    return;
                }
                catch (DuplicateCommitException) {
                    stream.ClearChanges();
                    return;
                }
                catch (ConcurrencyException e) {
                    var conflict = ThrowOnConflict(stream, commitEventCount);
                    stream.ClearChanges();

                    if (conflict)
                    {
                        throw new ConflictingCommandException(e.Message, e);
                    }
                }
                catch (StorageException e) {
                    throw new PersistenceException(e.Message, e);
                }
            }
        }
示例#4
0
        public void Store(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commit = new Commit
            {
                CommitId    = commitId,
                AggregateId = aggregate.Id,
                CommitStamp = DateTimeOffset.UtcNow
            };


            using (var session = string.IsNullOrWhiteSpace(_databaseName)
                ? _store.OpenSession()
                : _store.OpenSession(_databaseName))
            {
                session.Advanced.UseOptimisticConcurrency         = true;
                session.Advanced.AllowNonAuthoritativeInformation = false;

                var stream = session.Load <EventStream>(aggregate.Id.ToStringId <EventStream>());
                if (stream == null)
                {
                    stream = new EventStream
                    {
                        Id            = aggregate.Id.ToStringId <EventStream>(),
                        AggregateType = aggregate.GetType().FullName
                    };
                    session.Store(stream);
                }

                commit.CommitSequence = stream.Commits.Count + 1;
                commit.Events         = aggregate.GetUncommittedEvents()
                                        .Cast <object>()
                                        .Select(x => new Event {
                    Body = x, Headers = PrepareHeaders(updateHeaders)
                })
                                        .ToList();

                stream.Version  = aggregate.Version;
                stream.Snapshot = aggregate;
                stream.Commits.Add(commitId, commit);

                //pre-commit hooks
                if (_pipelineHooks.All(hook => hook.PreCommit(commit)))
                {
                    try
                    {
                        session.SaveChanges();
                        aggregate.ClearUncommittedEvents();
                    }
                    catch (Exception exception)
                    {
                        throw;
                    }
                }
            }
            //post commit hooks
            foreach (var hook in _pipelineHooks)
            {
                hook.PostCommit(commit, _databaseName);
            }
        }
        public async Task SaveAsync(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { CommitIdHeader, commitId },
                { this._aggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName      = this._aggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            var newEvents       = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var eventsToSave    = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();

            if (eventsToSave.Count < WritePageSize)
            {
                await this._eventDataFactory.AppendToStreamAsync(aggregate, expectedVersion, eventsToSave);
            }
            else
            {
                //TODO
            }

            aggregate.ClearUncommittedEvents();
        }
示例#6
0
        public virtual void Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var headers = PrepareHeaders(aggregate, updateHeaders);

            while (true)
            {
                var stream           = this.PrepareStream(aggregate, headers);
                var commitEventCount = stream.CommittedEvents.Count;

                try
                {
                    stream.CommitChanges(commitId);
                    aggregate.ClearUncommittedEvents();
                    return;
                }
                catch (DuplicateCommitException)
                {
                    stream.ClearChanges();
                    return;
                }
                catch (ConcurrencyException e)
                {
                    if (this.ThrowOnConflict(stream, commitEventCount))
                    {
                        throw new ConflictingCommandException(e.Message, e);
                    }

                    stream.ClearChanges();
                }
                catch (StorageException e)
                {
                    throw new PersistenceException(e.Message, e);
                }
            }
        }
        public OrderedEventPayload[] Save(IAggregate aggregate)
        {
            var events = aggregate.GetUncommittedEvents().ToArray();

            if (events.Any() == false)
            {
                return new OrderedEventPayload[] { }
            }
            ;                                         // Nothing to save

            var aggregateType = aggregate.GetType().Name;

            var originalVersion = aggregate.Version - events.Count() + 1;

            var eventsToSave = events
                               .Select(e => e.ToEventData(aggregateType, aggregate.Id, originalVersion++))
                               .ToArray();

            var storedAggregateVersion = _eventRepository.GetVersionByAggregateId(aggregate.Id);

            if (storedAggregateVersion.HasValue && storedAggregateVersion >= originalVersion)
            {
                throw new Exception("Concurrency exception");
            }

            var orderedEvents = _eventRepository.SaveEvents(eventsToSave);

            aggregate.ClearUncommittedEvents();

            return(orderedEvents);
        }
示例#8
0
        private async Task SaveAsync(IAggregate aggregate)
        {
            var events = aggregate.GetUncomittedEvents();

            foreach (var @event in events)
            {
                @event.SetAggregateId(aggregate.Id);
            }

            await domainObjectRepository.SaveAsync(aggregate, events, Guid.NewGuid());

            aggregate.ClearUncommittedEvents();
        }
        public async void Save(IAggregate aggregate, Guid commitId, IDictionary <string, object> updateHeaders = null)
        {
            // standard data for metadata portion of persisted event
            var commitHeaders = new Dictionary <string, object>
            {
                // handy tracking id
                { CommitIdHeader, commitId },
                // type of aggregate being persisted
                { AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            // add extra data to metadata portion of presisted event
            commitHeaders = (updateHeaders ?? new Dictionary <string, object>())
                            .Concat(commitHeaders)
                            .GroupBy(d => d.Key)
                            .ToDictionary(d => d.Key, d => d.First().Value);

            // streamname is created by func, by default agg type concat to agg id
            var streamName = _aggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            // get all uncommitted events
            var newEvents = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            // process events so they fit the expectations of GES
            var eventsToSave = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();
            // calculate the expected version of the agg root in event store to detirmine if concurrency conflict
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;

            // if numberr of events to save is small enough it can happen in one call
            if (eventsToSave.Count < WritePageSize)
            {
                await _eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave);
            }
            // otherwise batch events and start transaction
            else
            {
                var transaction = await _eventStoreConnection.StartTransactionAsync(streamName, expectedVersion);

                var position = 0;
                while (position < eventsToSave.Count)
                {
                    var pageEvents = eventsToSave.Skip(position).Take(WritePageSize);
                    await transaction.WriteAsync(pageEvents);

                    position += WritePageSize;
                }

                await transaction.CommitAsync();
            }

            aggregate.ClearUncommittedEvents();
        }
        public void Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { COMMIT_ID_HEADER, commitId },
                { AGGREGATE_CLR_TYPE_HEADER, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName      = m_AggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            var newEvents       = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion;
            var eventsToSave    = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();


            var transaction = m_EventStoreConnection.StartTransaction(streamName, expectedVersion);

            try
            {
                if (eventsToSave.Count < WRITE_PAGE_SIZE)
                {
                    transaction.Write(eventsToSave);
                }
                else
                {
                    var position = 0;
                    while (position < eventsToSave.Count)
                    {
                        var pageEvents = eventsToSave.Skip(position).Take(WRITE_PAGE_SIZE);
                        transaction.Write(pageEvents);
                        position += WRITE_PAGE_SIZE;
                    }
                }
                //TODO: not prod code. Need to arrange serialization, data saved to ES should be same as sent to queue
                foreach (var @event in newEvents)
                {
                    m_EventsPublisher.PublishEvent(@event);
                }
                transaction.Commit();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                //TODO:logging
                transaction.Rollback();
            }
            aggregate.ClearUncommittedEvents();
        }
示例#11
0
        public void Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { CommitIdHeader, commitId },
                { AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName      = _aggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            var newEvents       = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var eventsToSave    = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();

            if (eventsToSave.Count < WritePageSize)
            {
                _eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave).Wait();
            }
            else
            {
                var transaction = _eventStoreConnection.StartTransactionAsync(streamName, expectedVersion).Result;

                var position = 0;
                while (position < eventsToSave.Count)
                {
                    var pageEvents = eventsToSave.Skip(position).Take(WritePageSize);
                    transaction.WriteAsync(pageEvents).Wait();
                    position += WritePageSize;
                }

                transaction.CommitAsync().Wait();
            }
            if (_outBus != null)
            {
                foreach (var evt in newEvents)
                {
                    try
                    {
                        _outBus.Publish((Message)evt);
                    }
                    catch { }//TODO: see if we need to do something here
                }
            }
            aggregate.ClearUncommittedEvents();
        }
        public void Write(IAggregate aggregate, Guid commitId, Action<IDictionary<string, object>> updateHeaders)
        {
            var streamName = _aggregateIdToStreamName(aggregate.Category, aggregate.Id);
            var newEvents = aggregate.GetUncommittedEvents().Cast<object>().ToList();

            var eventsToSave = newEvents
                .Select(e => e.AsJsonEvent())
                .ToList();

            foreach (var eventData in eventsToSave)
            {
                var data = new StreamData(streamName, eventData);

                FakeDatabase.Events.Add(Guid.NewGuid(), data);
            }

            aggregate.ClearUncommittedEvents();
        }
        public void Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { CommitIdHeader, commitId },
                { AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName      = _aggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            var newEvents       = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var eventsToSave    = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();

            if (eventsToSave.Count < WritePageSize)
            {
                _eventStoreConnection.AppendToStream(streamName, expectedVersion, eventsToSave);
            }
            else
            {
                var transaction = _eventStoreConnection.StartTransaction(streamName, expectedVersion);

                var position = 0;
                while (position < eventsToSave.Count)
                {
                    var pageEvents = eventsToSave.Skip(position).Take(WritePageSize);
                    transaction.Write(pageEvents);
                    position += WritePageSize;
                }

                transaction.Commit();
            }

            foreach (var newEvent in newEvents)
            {
                _bus.Publish(newEvent);
            }

            aggregate.ClearUncommittedEvents();
        }
        public void Should_add_event_to_uncommitted_events_and_increase_version_when_raised()
        {
            var event1 = new MyEvent();
            var event2 = new MyEvent();

            var sut = new DO(Guid.NewGuid(), 10);

            IAggregate aggregate = sut;

            sut.RaiseTestEvent(event1);
            sut.RaiseTestEvent(event2);

            Assert.Equal(12, sut.Version);

            Assert.Equal(new IEvent[] { event1, event2 }, aggregate.GetUncomittedEvents().Select(x => x.Payload).ToArray());

            aggregate.ClearUncommittedEvents();

            Assert.Equal(0, sut.GetUncomittedEvents().Count);
        }
        public void Save(IAggregate aggregate)
        {
            var commitId = Guid.NewGuid();
            var events   = aggregate.GetUncommittedEvents().ToArray();

            if (events.Any() == false)
            {
                return;
            }
            var streamName      = GetTheStreamName(aggregate.GetType(), aggregate.Id);
            var originalVersion = aggregate.Version - events.Count();
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var commitHeaders   = new Dictionary <string, object>
            {
                { "CommitId", commitId },
                { "AggregateClrType", aggregate.GetType().AssemblyQualifiedName }
            };
            var eventsToSave = events.Select(e => e.ToEventData(commitHeaders)).ToList();

            connection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave).Wait();
            aggregate.ClearUncommittedEvents();
        }
示例#16
0
        public async Task Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { CommitIdHeader, commitId },
                { CommitDateHeader, DateTime.UtcNow },
                { AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName      = aggregateIdToStreamName(aggregate.GetType(), aggregate.Id.Value);
            var newEvents       = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion = aggregate.Version - newEvents.Count;
            var expectedVersion = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var eventsToSave    = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();

            if (eventsToSave.Count < WritePageSize)
            {
                eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave).Wait();
            }
            else
            {
                var transaction = eventStoreConnection.StartTransactionAsync(streamName, expectedVersion).Result;

                var position = 0;
                while (position < eventsToSave.Count)
                {
                    var pageEvents = eventsToSave.Skip(position).Take(WritePageSize);
                    await transaction.WriteAsync(pageEvents);

                    position += WritePageSize;
                }
                await transaction.CommitAsync();

                transaction.Dispose();
            }
            aggregate.ClearUncommittedEvents();
        }
示例#17
0
        public async Task <AggregateCommit> SaveAsync(IAggregate aggregate)
        {
            if (aggregate.Id == Guid.Empty)
            {
                throw new Exception(
                          $"The aggregate {aggregate.GetType().FullName} has tried to be saved with an empty id");
            }

            var uncommittedEvents = aggregate.GetUncommittedEvents().Cast <IEventData>().ToArray();
            var count             = 0;

            var metadata = string.Empty;

            try
            {
                var col = _db.GetCollection <LocalEventTable>();
                col.EnsureIndex(x => x.StreamId, false);

                col.InsertBulk(uncommittedEvents.Select(x => new LocalEventTable {
                    StreamId      = aggregate.Id,
                    Version       = x.Version,
                    TransactionId = _commandContext.Transaction.Id,
                    AppVersion    = _commandContext.AppVersion,
                    When          = x.TimeStamp,
                    Body          = JsonConvert.SerializeObject(x.Event, SerializerSettings),
                    Category      = aggregate.GetType().FullName,
                    Who           = _commandContext.ImpersonatorBy?.GuidId ?? _commandContext.By.GuidId,
                    BodyType      = x.Type.FullName
                }));
            }
            catch (Exception e)
            {
                throw new Exception(
                          $"The aggregate {aggregate.GetType().FullName} has tried to save events to an old version of an aggregate");
            }

            aggregate.ClearUncommittedEvents();
            return(new AggregateCommit(aggregate.Id, _commandContext.By.GuidId, metadata, uncommittedEvents));
        }
        public void Save(string bucketId, IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            Dictionary <string, object> headers = PrepareHeaders(aggregate, updateHeaders);

            while (true)
            {
                IEventStream stream           = PrepareStream(bucketId, aggregate, headers);
                int          commitEventCount = stream.CommittedEvents.Count;

                try
                {
                    stream.CommitChanges(commitId);
                    aggregate.ClearUncommittedEvents();
                    return;
                }
                catch (DuplicateCommitException)
                {
                    stream.ClearChanges();
                    // Issue: #4 and test: when_an_aggregate_is_persisted_using_the_same_commitId_twice
                    // should we rethtow the exception here? or provide a feedback whether the save was successful ?
                    return;
                }
                catch (ConcurrencyException e)
                {
                    var conflict = ThrowOnConflict(stream, commitEventCount);
                    stream.ClearChanges();

                    if (conflict)
                    {
                        throw new ConflictingCommandException(e.Message, e);
                    }
                }
                catch (StorageException e)
                {
                    throw new PersistenceException(e.Message, e);
                }
            }
        }
示例#19
0
 private void ClearEvents(IAggregate agr)
 {
     agr.ClearUncommittedEvents();
 }
示例#20
0
 /// <summary>
 /// Stores the aggregate changes.
 /// </summary>
 /// <param name="aggregate"></param>
 public void SaveChanges(IAggregate aggregate)
 {
     SaveEvents(new AggregateKey(aggregate), aggregate.Revision, aggregate.UncommittedChanges());
     aggregate.ClearUncommittedEvents();
 }
示例#21
0
        public void Save(IAggregate aggregate, Guid commitId, Action <IDictionary <string, object> > updateHeaders)
        {
            var commitHeaders = new Dictionary <string, object>
            {
                { CommitIdHeader, commitId },
                { AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName }
            };

            updateHeaders(commitHeaders);

            var streamName         = _aggregateIdToStreamName(aggregate.GetType(), aggregate.Id);
            var categoryStreamName = _aggregateTypeToCategoryStreamName(aggregate.GetType());
            var newEvents          = aggregate.GetUncommittedEvents().Cast <object>().ToList();
            var originalVersion    = aggregate.Version - newEvents.Count;
            var expectedVersion    = originalVersion == 0 ? ExpectedVersion.NoStream : originalVersion - 1;
            var eventsToSave       = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();

            List <EventData> stream;

            _store.TryGetValue(streamName, out stream);
            List <EventData> catStream;

            _store.TryGetValue(categoryStreamName, out catStream);



            if (stream == null)
            {
                if (expectedVersion == ExpectedVersion.Any || expectedVersion == ExpectedVersion.NoStream)
                {
                    stream = new List <EventData>();
                    _store.Add(streamName, stream);
                    if (catStream == null)
                    {
                        catStream = new List <EventData>();
                        _store.Add(categoryStreamName, catStream);
                    }
                }
                else
                {
                    throw new WrongExpectedVersionException("Stream " + streamName + " does not exist.");
                }
            }

            if (stream.Count != 0 && stream.Count - 1 != expectedVersion) // a new stream will be @ version 0
            {
                throw new AggregateException(new WrongExpectedVersionException(
                                                 $"Stream {streamName} at version {stream.Count}, expected version {expectedVersion}"));
            }


            stream.AddRange(eventsToSave);
            catStream?.AddRange(eventsToSave);

            foreach (var evt in eventsToSave)
            {
                var etName = _eventNameToEventTypeStreamName(evt.Type);
                List <EventData> etStream;
                if (!_store.TryGetValue(etName, out etStream))
                {
                    etStream = new List <EventData>();
                    _store.Add(etName, etStream);
                }
                etStream.Add(evt);
            }

            foreach (var @event in aggregate.GetUncommittedEvents().Cast <object>().Where(@event => (@event as Message) != null))
            {
                _bus.Publish(@event as Message);
                _history.Add(new Tuple <string, Message>(streamName, @event as Message));
            }
            aggregate.ClearUncommittedEvents();
        }
示例#22
0
 /// <summary>
 /// Stores the aggregate changes.
 /// </summary>
 /// <param name="aggregate"></param>
 public void SaveChanges(IAggregate aggregate)
 {
     SaveEvents(new AggregateKey(aggregate), aggregate.Revision, aggregate.UncommittedChanges());
     aggregate.ClearUncommittedEvents();
 }
示例#23
0
 public static void ClearEvents(this IAggregate aggregate)
 {
     aggregate.ClearUncommittedEvents();
 }
示例#24
0
 private void Hydrate(IAggregate aggregate)
 {
     GetOrAddEventList(aggregate.Id).ForEach(aggregate.ApplyEvent);
     aggregate.ClearUncommittedEvents();
 }