/// <summary> /// Initializes a new instance of the <see cref="AtomFeed"/> class. /// </summary> /// <param name="id">The ID of the Atom Feed.</param> /// <param name="title">The title of the Atom Feed.</param> /// <param name="updated"> /// The date and time the Atom Feed was last updated. /// </param> /// <param name="author">The author of the Atom Feed.</param> /// <param name="entries">The entries of the Atom Feed.</param> /// <param name="links">The links of the Atom Feed itself.</param> /// <remarks> /// <para> /// All values passed into this constructor are subsequently available /// as properties on the instance. /// </para> /// </remarks> /// <exception cref="System.ArgumentNullException"> /// <paramref name="title" /> /// or /// <paramref name="author" /> /// or /// <paramref name="entries" /> /// or /// <paramref name="links" /> /// is <see langword="null" />. /// </exception> public AtomFeed( UuidIri id, string title, DateTimeOffset updated, AtomAuthor author, IEnumerable <AtomEntry> entries, IEnumerable <AtomLink> links) { if (title == null) { throw new ArgumentNullException("title"); } if (author == null) { throw new ArgumentNullException("author"); } if (entries == null) { throw new ArgumentNullException("entries"); } if (links == null) { throw new ArgumentNullException("links"); } this.id = id; this.title = title; this.updated = updated; this.author = author; this.entries = entries; this.links = links; }
/// <summary> /// Initializes a new instance of the <see cref="AtomEntry"/> class. /// </summary> /// <param name="id">The ID of the entry.</param> /// <param name="title">The title of the entry.</param> /// <param name="published">The date and time of publication.</param> /// <param name="updated"> /// The date and time the entry was last updated. /// </param> /// <param name="author">The author of the entry.</param> /// <param name="content">The content of the entry.</param> /// <param name="links">The links of the entry.</param> /// <remarks> /// <para> /// The constructor arguments are subsequently available on the object /// as properties. /// </para> /// </remarks> /// <exception cref="System.ArgumentNullException"> /// <paramref name="title" /> /// or /// <paramref name="author" /> /// or /// <paramref name="content" /> /// or /// <paramref name="content" /> is <see langword="null" />. /// </exception> public AtomEntry( UuidIri id, string title, DateTimeOffset published, DateTimeOffset updated, AtomAuthor author, XmlAtomContent content, IEnumerable <AtomLink> links) { if (title == null) { throw new ArgumentNullException("title"); } if (author == null) { throw new ArgumentNullException("author"); } if (content == null) { throw new ArgumentNullException("content"); } if (links == null) { throw new ArgumentNullException("links"); } this.id = id; this.title = title; this.published = published; this.updated = updated; this.author = author; this.content = content; this.links = links; }
private static IEnumerable <AtomLink> ReplacePreviousLink( IEnumerable <AtomLink> links, UuidIri previousId) { return(links .Where(l => !AtomEventStream.IsPreviousFeedLink(l)) .Concat(new[] { AtomEventStream.CreatePreviousLinkFrom(previousId) })); }
private AtomFeed CreateNewIndex( AtomEntry entry, IEnumerable <AtomLink> links, UuidIri previousId, DateTimeOffset now) { return(this.CreateNewIndex( new[] { entry }, ReplacePreviousLink(links, previousId), now)); }
/// <summary> /// Creates an <see cref="AtomEntry" /> instance from XML. /// </summary> /// <param name="xmlReader"> /// The <see cref="XmlReader" /> containing the XML representation of /// the Atom Entry. /// </param> /// <param name="serializer"> /// The <see cref="IContentSerializer" /> used to serialize custom XML /// content. /// </param> /// <returns> /// A new instance of <see cref="AtomEntry" /> containing the data from /// the XML representation of the Atom Entry contained in /// <paramref name="xmlReader" />. /// </returns> /// <exception cref="System.ArgumentNullException"> /// <paramref name="serializer" /> is <see langword="null" />. /// </exception> public static AtomEntry ReadFrom( XmlReader xmlReader, IContentSerializer serializer) { if (serializer == null) { throw new ArgumentNullException("serializer"); } var navigator = new XPathDocument(xmlReader).CreateNavigator(); var resolver = new XmlNamespaceManager(new NameTable()); resolver.AddNamespace("atom", "http://www.w3.org/2005/Atom"); var id = navigator .Select("/atom:entry/atom:id", resolver).Cast <XPathNavigator>() .Single().Value; var title = navigator .Select("/atom:entry/atom:title[@type = 'text']", resolver).Cast <XPathNavigator>() .Single().Value; var published = navigator .Select("/atom:entry/atom:published", resolver).Cast <XPathNavigator>() .Single().Value; var updated = navigator .Select("/atom:entry/atom:updated", resolver).Cast <XPathNavigator>() .Single().Value; var author = navigator .Select("/atom:entry/atom:author", resolver).Cast <XPathNavigator>() .Single().ReadSubtree(); var content = navigator .Select("/atom:entry/atom:content[@type = 'application/xml']", resolver).Cast <XPathNavigator>() .Single().ReadSubtree(); var links = navigator .Select("/atom:entry/atom:link", resolver).Cast <XPathNavigator>(); return(new AtomEntry( UuidIri.Parse(id), title, DateTimeOffset.Parse(published, CultureInfo.InvariantCulture), DateTimeOffset.Parse(updated, CultureInfo.InvariantCulture), AtomAuthor.ReadFrom(author), XmlAtomContent.ReadFrom(content, serializer), links.Select(x => AtomLink.ReadFrom(x.ReadSubtree())))); }
private static AtomFeed CreatePreviousPageFrom( AtomFeed index, UuidIri previousId, DateTimeOffset now) { return(new AtomFeed( previousId, "Partial event stream", now, new AtomAuthor("Grean"), index.Entries, index.Links .Where(AtomEventStream.IsPreviousFeedLink) .Concat(new[] { AtomEventStream.CreateSelfLinkFrom(previousId) }))); }
/// <summary> /// Initializes a new instance of the <see cref="FifoEvents{T}" /> /// class. /// </summary> /// <param name="id">The ID of the event stream.</param> /// <param name="storage"> /// The underlying storage mechanism from which to read. /// </param> /// <param name="serializer"> /// The serializer used to serialize and deserialize items to a format /// compatible with Atom. The object supplied via this constructor /// parameter is subsequently available via the /// <see cref="Serializer" /> property. /// </param> /// <remarks> /// <para> /// The <paramref name="id" /> is the ID of a single event stream. Each /// event stream has its own ID. If you need more than a single event /// stream (e.g. if you are implementing the Aggregate Root pattern), /// each event stream should have a separate ID. /// </para> /// <para> /// The <paramref name="storage" /> value can be any implementation of /// <see cref="IAtomEventStorage" />. Built-in implementatoins include /// <see cref="AtomEventsInMemory" /> and /// <see cref="AtomEventsInFiles" />. /// </para> /// </remarks> /// <exception cref="System.ArgumentNullException"> /// <paramref name="storage" /> or <paramref name="serializer" /> is /// <see langword="null" /> /// </exception> /// <seealso cref="FifoEvents{T}" /> /// <seealso cref="IContentSerializer" /> /// <seealso cref="AtomEventsInMemory" /> /// <seealso cref="AtomEventsInFiles" /> /// <seealso cref="IAtomEventStorage" /> public FifoEvents( UuidIri id, IAtomEventStorage storage, IContentSerializer serializer) { if (storage == null) { throw new ArgumentNullException("storage"); } if (serializer == null) { throw new ArgumentNullException("serializer"); } this.id = id; this.storage = storage; this.serializer = serializer; }
/// <summary> /// Initializes a new instance of the <see cref="AtomEventStream{T}" /> /// class. /// </summary> /// <param name="id">The ID of the event stream.</param> /// <param name="storage"> /// The underlying storage mechanism to use. /// </param> /// <param name="pageSize"> /// The maxkum page size; that is: the maximum number of instances of /// T stored in a single Atom feed page. /// </param> /// <param name="contentSerializer"> /// The serializer used to serialize and deserialize items to a format /// compatible with Atom. The object supplied via this constructor /// parameter is subsequently available via the /// <see cref="ContentSerializer" /> property. /// </param> /// <remarks> /// <para> /// The <paramref name="id" /> is the ID of a single event stream. Each /// event stream has its own ID. If you need more than a single event /// stream (e.g. if you are implementing the Aggregate Root pattern), /// each event stream should have a separate ID. /// </para> /// <para> /// The <paramref name="storage" /> value can be any implementation of /// <see cref="IAtomEventStorage" />. Built-in implementatoins include /// <see cref="AtomEventsInMemory" /> and /// <see cref="AtomEventsInFiles" />. /// </para> /// </remarks> /// <seealso cref="AtomEventStream{T}" /> /// <seealso cref="ContentSerializer" /> /// <seealso cref="AtomEventsInMemory" /> /// <seealso cref="AtomEventsInFiles" /> /// <seealso cref="IAtomEventStorage" /> public AtomEventStream( UuidIri id, IAtomEventStorage storage, int pageSize, IContentSerializer contentSerializer) { if (storage == null) { throw new ArgumentNullException("storage"); } if (contentSerializer == null) { throw new ArgumentNullException("contentSerializer"); } this.id = id; this.storage = storage; this.pageSize = pageSize; this.serializer = contentSerializer; }
/// <summary> /// Appends an event to the event stream. /// </summary> /// <param name="event"> /// The event to append to the event stream. /// </param> /// <returns> /// A <see cref="Task" /> representing the asynchronous operation of /// appending the event to the event stream. /// </returns> /// <remarks> /// <para> /// This method appends <paramref name="event" /> to the current event /// stream. Appending an event indicates that it happened /// <em>after</em> all previous events. However, keep in mind that /// since <see cref="AtomEventStream{T}" /> iterates over the event /// stream from newest to oldest event, the newly appended event will /// also be the first item to be enumerated, because it's the most /// recent event. /// </para> /// <para> /// Since this method conceptually involves writing the event to the /// underlying <see cref="Storage" />, it may take significant time to /// complete; for that reason, it's an asynchronous method, returning a /// <see cref="Task" />. The operation is not guaranteed to be complete /// before the Task completes successfully. /// </para> /// <para> /// When updating the underlying Storage, the method typically only /// updates the index feed, using /// <see cref="IAtomEventStorage.CreateFeedWriterFor(AtomFeed)" />. /// However, when the number of entries in the index surpasses /// <see cref="PageSize" />, the oldest entries are moved to a new, /// "previous" feed page. This page is also written using the /// CreateFeedWriterFor method, and only after this succeeds is the /// index updated. Since these two operations are not guaranteed to /// happen within an ACID transaction, it's possible that the /// "previous" page is saved, but that the update of the index fails. /// If the underlying storage throws an exception at that point, that /// exception will bubble up to the caller of the AppendAsync method. /// It's up to the caller to retry the operation. /// </para> /// <para> /// However, in that situation, an orphaned Atom feed page is likely to /// have been left in the underlying storage. This doesn't affect /// consistency of the system, but may take up unnecessary disk space. /// If this is the case, a separate clean-up task should find and /// delete orphaned pages. /// </para> /// </remarks> public Task AppendAsync(T @event) { if (@event == null) { throw new ArgumentNullException("event"); } return(Task.Factory.StartNew(() => { var now = DateTimeOffset.Now; var entry = CreateEntry(@event, now); var index = this.ReadIndex(); if (index.Entries.Count() >= this.pageSize) { var previousId = UuidIri.NewId(); var previousFeed = CreatePreviousPageFrom(index, previousId, now); var newIndex = this.CreateNewIndex(entry, index.Links, previousId, now); using (var w = this.storage.CreateFeedWriterFor(previousFeed)) previousFeed.WriteTo(w, this.serializer); using (var w = this.storage.CreateFeedWriterFor(newIndex)) newIndex.WriteTo(w, this.serializer); } else { var newIndex = this.AddEntryTo(index, entry, now); using (var w = this.storage.CreateFeedWriterFor(newIndex)) newIndex.WriteTo(w, this.serializer); } })); }