private void WriteEntryToNewPage(
            AtomEntry entry,
            AppendContext context)
        {
            var newAddress = this.CreateNewFeedAddress();
            var newPage    = this.ReadPage(newAddress);

            newPage = AddEntryTo(newPage, entry, context.Now);

            var nextLink = AtomLink.CreateNextLink(newAddress);

            var previousPage = context.LastPage
                               .WithLinks(context.LastPage.Links.Concat(new[] { nextLink }));

            var previousLink = previousPage.Links
                               .Single(l => l.IsSelfLink)
                               .ToPreviousLink();

            newPage = newPage.WithLinks(
                newPage.Links.Concat(new[] { previousLink }));
            var index = context.Index.WithLinks(context.Index.Links
                                                .Where(l => !l.IsLastLink)
                                                .Concat(new[] { nextLink.ToLastLink() }));

            this.Write(newPage);
            this.Write(previousPage);
            try { this.Write(index); } catch { }
        }
        /// <summary>
        /// Creates a new, empty feed.
        /// </summary>
        /// <param name="href">The address of the feed.</param>
        /// <returns>A new, empty Atom Feed.</returns>
        /// <remarks>
        /// <para>
        /// <paramref name="href" /> is expected to contain an
        /// <see cref="Uri" /> in the last segment. This Uri is used as the ID
        /// for the new Atom Feed.
        /// </para>
        /// </remarks>
        public static XmlReader CreateNewFeed(Uri href)
        {
            var id  = GetIdFromHref(href);
            var xml = new AtomFeed(
                id,
                "Index of event stream " + id,
                DateTimeOffset.Now,
                new AtomAuthor("Grean"),
                Enumerable.Empty <AtomEntry>(),
                new[]
            {
                AtomLink.CreateSelfLink(href)
            })
                      .ToXmlString((IContentSerializer)null);

            var sr = new StringReader(xml);

            try
            {
                return(XmlReader.Create(
                           sr,
                           new XmlReaderSettings {
                    CloseInput = true
                }));
            }
            catch
            {
                sr.Dispose();
                throw;
            }
        }
        public void CreateViaLinkReturnsCorrectResult(
            AtomLink link)
        {
            AtomLink actual = AtomLink.CreateViaLink(link.Href);

            var expected = link.WithRel("via");
            Assert.Equal(expected, actual);
        }
Beispiel #4
0
        /// <summary>
        /// Adds an <see cref="AtomLink" /> to the Atom Feed's collection of
        /// <see cref="Links" />.
        /// </summary>
        /// <param name="newLink">The link to be added.</param>
        /// <returns>
        /// A new instance of <see cref="AtomFeed" /> with the new link added.
        /// </returns>
        /// <remarks>
        /// <para>
        /// This method doesn't mutate the instance upon which it is invoked,
        /// but return a new instance with all other properties held constant,
        /// but <paramref name="newLink" /> added to the new instance's
        /// collection of <see cref="Links" />. The new copy also contains all
        /// the links from the original instance.
        /// </para>
        /// <para>
        /// The exact position of the new link relative to the original links
        /// is not guaranteed to be deterministic.
        /// </para>
        /// </remarks>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="newLink" /> is <see langword="null" />.
        /// </exception>
        public AtomFeed AddLink(AtomLink newLink)
        {
            if (newLink == null)
            {
                throw new ArgumentNullException("newLink");
            }

            return(this.WithLinks(this.links.Concat(new[] { newLink })));
        }
        public void AddLinkReturnsCorrectResult(
            AtomFeed sut,
            AtomLink newLink)
        {
            AtomFeed actual = sut.AddLink(newLink);

            var expected = sut.AsSource().OfLikeness<AtomFeed>()
                .With(x => x.Links).EqualsWhen(
                    (s, d) => sut.Links.Concat(new[] { newLink }).SequenceEqual(d.Links));
            expected.ShouldEqual(actual);
        }
Beispiel #6
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()))));
        }
Beispiel #7
0
        /// <summary>
        /// Parses the specified XML into an instance of
        /// <see cref="AtomLink" />.
        /// </summary>
        /// <param name="xml">
        /// A string of characters containing the XML representation of an Atom
        /// link.
        /// </param>
        /// <returns>
        /// A new instance of <see cref="AtomLink" /> containing the data from
        /// the supplied <paramref name="xml" />.
        /// </returns>
        public static AtomLink Parse(string xml)
        {
            var sr = new StringReader(xml);

            try
            {
                using (var r = XmlReader.Create(sr))
                {
                    sr = null;
                    return(AtomLink.ReadFrom(r));
                }
            }
            finally
            {
                if (sr != null)
                {
                    sr.Dispose();
                }
            }
        }
        private AppendContext Prepare(DateTimeOffset now)
        {
            var index     = this.ReadIndex();
            var firstLink = index.Links
                            .Where(l => l.IsFirstLink)
                            .DefaultIfEmpty(AtomLink.CreateFirstLink(this.CreateNewFeedAddress()))
                            .Single();

            index = index.WithLinks(index.Links.Union(new[] { firstLink }));

            var lastLink      = index.Links.SingleOrDefault(l => l.IsLastLink);
            var lastLinkAdded = false;

            if (lastLink == null)
            {
                lastLink      = firstLink.ToLastLink();
                lastLinkAdded = true;
            }
            var lastPage          = this.ReadTrueLastPage(lastLink.Href);
            var lastLinkCorrected = false;

            if (lastPage.Links.Single(l => l.IsSelfLink).Href != lastLink.Href)
            {
                lastLink          = lastPage.Links.Single(l => l.IsSelfLink).ToLastLink();
                lastLinkCorrected = true;
            }
            index = index.WithLinks(index.Links
                                    .Where(l => !l.IsLastLink)
                                    .Concat(new[] { lastLink }));

            return(new AppendContext(
                       index,
                       lastPage,
                       now,
                       lastLinkAdded,
                       lastLinkCorrected));
        }
        public void WriteToXmlWriterWritesCorrectXml(
            AtomLink sut)
        {
            // Fixture setup
            var sb = new StringBuilder();
            using (var w = XmlWriter.Create(sb))
            {
                // Exercise system
                sut.WriteTo(w);

                // Verify outcome
                w.Flush();

                var expected = XDocument.Parse(
                    "<link" +
                    " href=\"" + sut.Href.ToString() + "\"" +
                    " rel=\"" + sut.Rel + "\"" +
                    " xmlns=\"http://www.w3.org/2005/Atom\" />");

                var actual = XDocument.Parse(sb.ToString());
                Assert.Equal(expected, actual, new XNodeEqualityComparer());
            }
            // Teardown
        }
        public void WithRelReturnsCorrectResult(
            AtomLink sut,
            string newRel)
        {
            AtomLink actual = sut.WithRel(newRel);

            var expected = sut.AsSource().OfLikeness<AtomLink>()
                .With(x => x.Rel).EqualsWhen(
                    (s, d) => object.Equals(newRel, d.Rel));
            expected.ShouldEqual(actual);
        }
        public void WithHrefReturnsCorrectResult(
            AtomLink sut,
            Uri newHref)
        {
            AtomLink actual = sut.WithHref(newHref);

            var expected = sut.AsSource().OfLikeness<AtomLink>()
                .With(x => x.Href).EqualsWhen(
                    (s, d) => object.Equals(newHref, d.Href));
            expected.ShouldEqual(actual);
        }
 public void IsPreviousLinkReturnsFalsForNonPreviousLink(
     AtomLink sut)
 {
     Assert.NotEqual("previous", sut.Rel);
     var actual = sut.IsPreviousLink;
     Assert.False(actual, "Should not be previous link.");
 }
 private static AtomLink MakeSelfLinkIndexed(AtomLink link)
 {
     if (link.IsSelfLink)
     {
         var segment = GetIdFromHref(link.Href);
         var indexedHref = segment + "/" + segment;
         return link.WithHref(new Uri(indexedHref, UriKind.Relative));
     }
     else
         return link;
 }
 public void RelIsCorrect([Frozen]string expected, AtomLink sut)
 {
     string actual = sut.Rel;
     Assert.Equal(expected, actual);
 }
 public void HrefIsCorrect([Frozen]Uri expected, AtomLink sut)
 {
     Uri actual = sut.Href;
     Assert.Equal(expected, actual);
 }
 public void ReadFromWhenHrefIsRelativeReturnsCorrectResult(
     AtomLink seed,
     string relativeUrl,
     IContentSerializer dummySerializer)
 {
     var expected = seed.WithHref(new Uri(relativeUrl, UriKind.Relative));
     using (var sr = new StringReader(expected.ToXmlString(dummySerializer)))
     using (var r = XmlReader.Create(sr))
     {
         AtomLink actual = AtomLink.ReadFrom(r);
         Assert.Equal(expected, actual);
     }
 }
 public void ReadFromXmlWithoutHrefThrows(
     string attribute,
     AtomLink seed,
     IContentSerializer dummySerializer)
 {
     XNamespace atom = "http://www.w3.org/2005/Atom";
     var xml = XDocument.Parse(seed.ToXmlString(dummySerializer));
     xml.Root.Attribute(attribute).Remove();
     using(var r = xml.CreateReader())
     {
         var e = Assert.Throws<ArgumentException>(
             () => AtomLink.ReadFrom(r));
         Assert.Contains(attribute, e.Message);
     }
 }
 public void ReadFromReturnsCorrectResult(
     AtomLink expected,
     IContentSerializer dummySerializer)
 {
     using (var sr = new StringReader(expected.ToXmlString(dummySerializer)))
     using (var r = XmlReader.Create(sr))
     {
         AtomLink actual = AtomLink.ReadFrom(r);
         Assert.Equal(expected, actual);
     }
 }
 public void IsViaLinkReturnsFalsForNonViaLink(
     AtomLink sut)
 {
     Assert.NotEqual("via", sut.Rel);
     var actual = sut.IsViaLink;
     Assert.False(actual, "Should not be via link.");
 }
 public void IsSelfLinkReturnsFalsForUnSelfLink(
     AtomLink sut)
 {
     Assert.NotEqual("self", sut.Rel);
     var actual = sut.IsSelfLink;
     Assert.False(actual, "Should not be self link.");
 }
 public void IsLastLinkReturnsFalseForNonLastLink(AtomLink sut)
 {
     Assert.NotEqual("last", sut.Rel);
     var actual = sut.IsLastLink;
     Assert.False(actual, "Should not be last link.");
 }
Beispiel #22
0
        /// <summary>
        /// Adds an <see cref="AtomLink" /> to the Atom Feed's collection of
        /// <see cref="Links" />.
        /// </summary>
        /// <param name="newLink">The link to be added.</param>
        /// <returns>
        /// A new instance of <see cref="AtomFeed" /> with the new link added.
        /// </returns>
        /// <remarks>
        /// <para>
        /// This method doesn't mutate the instance upon which it is invoked,
        /// but return a new instance with all other properties held constant,
        /// but <paramref name="newLink" /> added to the new instance's
        /// collection of <see cref="Links" />. The new copy also contains all
        /// the links from the original instance.
        /// </para>
        /// <para>
        /// The exact position of the new link relative to the original links
        /// is not guaranteed to be deterministic.
        /// </para>
        /// </remarks>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="newLink" /> is <see langword="null" />.
        /// </exception>
        public AtomFeed AddLink(AtomLink newLink)
        {
            if (newLink == null)
                throw new ArgumentNullException("newLink");

            return this.WithLinks(this.links.Concat(new[] { newLink }));
        }
        public void EqualsReturnsCorrectResult(
            bool expected,
            string sutRel,
            string otherRel,
            string sutHref,
            string otherHref)
        {
            var sut = new AtomLink(sutRel, new Uri(sutHref, UriKind.Relative));
            var other = new AtomLink(otherRel, new Uri(otherHref, UriKind.Relative));

            var actual = sut.Equals(other);

            Assert.Equal(expected, actual);
        }
 public void SutDoesNotEqualAnonymousOther(
     AtomLink sut,
     object anonymous)
 {
     var actual = sut.Equals(anonymous);
     Assert.False(actual);
 }
        public void GetHashCodeReturnsCorrectResult(AtomLink sut)
        {
            var actual = sut.GetHashCode();

            var expected =
                sut.Rel.GetHashCode() ^
                sut.Href.GetHashCode();
            Assert.Equal(expected, actual);
        }
 public void SutIsXmlWritable(AtomLink sut)
 {
     Assert.IsAssignableFrom<IXmlWritable>(sut);
 }
        /// <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.
        /// </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" />, a new feed page is created for the new
        /// entry. This page is also written using the CreateFeedWriterFor
        /// method, and only after this succeeds is the old feed page updated
        /// with a link to the new page. Since these two operations are not
        /// guaranteed to happen within an ACID transaction, it's possible that
        /// the new page is saved, but that the update of the old page 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>
        /// <example>
        /// This example shows how to create a UserCreated event and write it
        /// using the AppendAsync method. Notice that since
        /// AtomEventObserver&lt;T&gt; uses the standard Task Parallel Library
        /// (TPL) model, you can use it with 'async' and 'await'.
        /// <code>
        /// var obs = new AtomEventObserver&lt;IUserEvent&gt;(
        ///     eventStreamId, // a Guid
        ///     pageSize,      // an Int32
        ///     storage,       // an IAtomEventStorage object
        ///     serializer);   // an IContentSerializer object
        ///
        /// var userCreated = new UserCreated
        /// {
        ///     UserId = eventStreamId,
        ///     UserName = "******",
        ///     Password = "******",
        ///     Email = "*****@*****.**"
        /// };
        /// await obs.AppendAsync(userCreated);
        /// </code>
        /// </example>
        /// <seealso cref="OnNext" />
        public Task AppendAsync(T @event)
        {
            return(Task.Factory.StartNew(() =>
            {
                var now = DateTimeOffset.Now;

                var index = this.ReadIndex();
                var firstLink = index.Links
                                .Where(l => l.IsFirstLink)
                                .DefaultIfEmpty(AtomLink.CreateFirstLink(this.CreateNewFeedAddress()))
                                .Single();
                var lastLink = index.Links.SingleOrDefault(l => l.IsLastLink);
                var lastLinkChanged = false;
                if (lastLink == null)
                {
                    lastLink = firstLink.ToLastLink();
                    lastLinkChanged = true;
                }
                var lastPage = this.ReadLastPage(lastLink.Href);
                if (lastPage.Links.Single(l => l.IsSelfLink).Href != lastLink.Href)
                {
                    lastLink = lastPage.Links.Single(l => l.IsSelfLink).ToLastLink();
                    lastLinkChanged = true;
                }
                index = index.WithLinks(index.Links.Union(new[] { firstLink }));
                index = index.WithLinks(index.Links
                                        .Where(l => !l.IsLastLink)
                                        .Concat(new[] { lastLink }));

                var entry = CreateEntry(@event, now);

                if (lastPage.Entries.Count() >= this.pageSize)
                {
                    var nextAddress = this.CreateNewFeedAddress();
                    var nextPage = this.ReadPage(nextAddress);
                    nextPage = AddEntryTo(nextPage, entry, now);

                    var nextLink = AtomLink.CreateNextLink(nextAddress);

                    var previousPage = lastPage
                                       .WithLinks(lastPage.Links.Concat(new[] { nextLink }));

                    var previousLink = previousPage.Links
                                       .Single(l => l.IsSelfLink)
                                       .ToPreviousLink();

                    nextPage = nextPage.WithLinks(
                        nextPage.Links.Concat(new[] { previousLink }));
                    index = index.WithLinks(index.Links
                                            .Where(l => !l.IsLastLink)
                                            .Concat(new[] { nextLink.ToLastLink() }));

                    this.Write(nextPage);
                    this.Write(previousPage);
                    this.Write(index);
                }
                else
                {
                    lastPage = AddEntryTo(lastPage, entry, now);

                    this.Write(lastPage);
                    if (lastLinkChanged)
                    {
                        this.Write(index);
                    }
                }
            }));
        }
        public void ToViaLinkReturnsCorrectResult(
            AtomLink sut)
        {
            Assert.NotEqual("via", sut.Rel);

            AtomLink actual = sut.ToViaLink();

            var expected = sut.WithRel("via");
            Assert.Equal(expected, actual);
        }
 public void SutCanRoundTripToString(
     AtomLink expected,
     IContentSerializer dummySerializer)
 {
     var xml = expected.ToXmlString(dummySerializer);
     AtomLink actual = AtomLink.Parse(xml);
     Assert.Equal(expected, actual);
 }
 public void IsNextLinkReturnsFalsForNonNextLink(
     AtomLink sut)
 {
     Assert.NotEqual("next", sut.Rel);
     var actual = sut.IsNextLink;
     Assert.False(actual, "Should not be next link.");
 }