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); }
/// <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); }
/// <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())))); }
/// <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."); }
/// <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<T> uses the standard Task Parallel Library /// (TPL) model, you can use it with 'async' and 'await'. /// <code> /// var obs = new AtomEventObserver<IUserEvent>( /// 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."); }