示例#1
0
        /// <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;
        }
示例#2
0
        /// <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;
        }
示例#3
0
 private static IEnumerable <AtomLink> ReplacePreviousLink(
     IEnumerable <AtomLink> links,
     UuidIri previousId)
 {
     return(links
            .Where(l => !AtomEventStream.IsPreviousFeedLink(l))
            .Concat(new[]
     {
         AtomEventStream.CreatePreviousLinkFrom(previousId)
     }));
 }
示例#4
0
 private AtomFeed CreateNewIndex(
     AtomEntry entry,
     IEnumerable <AtomLink> links,
     UuidIri previousId,
     DateTimeOffset now)
 {
     return(this.CreateNewIndex(
                new[] { entry },
                ReplacePreviousLink(links, previousId),
                now));
 }
示例#5
0
        /// <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()))));
        }
示例#6
0
 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)
     })));
 }
示例#7
0
        /// <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;
        }
示例#8
0
        /// <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;
        }
示例#9
0
        /// <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);
                }
            }));
        }