public void Can_roundtrip_DispatchItem_with_complex_event()
 {
     var body = new XDoc("msg").Elem("foo", "bar");
     var channel = new XUri("channel://foo.com/bar");
     var resource = new XUri("http://foo.com/baz/0");
     var origin1 = new XUri("http://foo.com/baz/1");
     var origin2 = new XUri("http://foo.com/baz/2");
     var recipient1 = new DispatcherRecipient(new XUri("http://recipient1"));
     var recipient2 = new DispatcherRecipient(new XUri("http://recipient2"));
     var via1 = new XUri("http://via1");
     var via2 = new XUri("http://via2");
     var ev = new DispatcherEvent(body, channel, resource, origin1, origin2);
     ev = ev.WithRecipient(false, recipient1, recipient2).WithVia(via1).WithVia(via2);
     var item = new DispatchItem(
         new XUri("http://foo"),
         ev,
         "abc"
     );
     var serializer = new DispatchItemSerializer();
     var stream = serializer.ToStream(item);
     var item2 = serializer.FromStream(stream);
     Assert.AreEqual(item.Uri, item2.Uri, "uri mismatch");
     Assert.AreEqual(item.Location, item2.Location, "location mismatch");
     Assert.AreEqual(item.Event.Id, item2.Event.Id, "id mismatch");
     Assert.AreEqual(body.ToCompactString(), item2.Event.AsDocument().ToCompactString(), "body mismatch");
     Assert.AreEqual(channel, item2.Event.Channel, "channel mismatch");
     Assert.AreEqual(resource, item2.Event.Resource, "resource mismatch");
     Assert.AreEqual(origin1, item2.Event.Origins[0], "first origin mismatch");
     Assert.AreEqual(origin2, item2.Event.Origins[1], "second origin mismatch");
     Assert.AreEqual(recipient1.Uri, item2.Event.Recipients[0].Uri, "first recipient mismatch");
     Assert.AreEqual(recipient2.Uri, item2.Event.Recipients[1].Uri, "second recipient mismatch");
     Assert.AreEqual(via1, item2.Event.Via[0], "first via mismatch");
     Assert.AreEqual(via2, item2.Event.Via[1], "second via mismatch");
 }
Exemple #2
0
 public void Add_recipients_to_event()
 {
     XDoc msg = new XDoc("msg");
     DispatcherRecipient r1 = new DispatcherRecipient(new XUri("mailto:///[email protected]"));
     DispatcherRecipient r2 = new DispatcherRecipient(new XUri("mailto:///[email protected]"));
     DispatcherRecipient r3 = new DispatcherRecipient(new XUri("mailto:///[email protected]"));
     DispatcherEvent ev1 = new DispatcherEvent(msg, new XUri("channel://foo.com/bar"), new XUri("http://foo.com/baz"));
     DispatcherEvent ev2 = ev1.WithRecipient(false, r1);
     Assert.AreEqual(0, ev1.Recipients.Length);
     Assert.AreEqual(1, ev2.Recipients.Length);
     Assert.AreEqual(r1, ev2.Recipients[0]);
     DispatcherEvent ev3 = ev2.WithRecipient(false, r2, r3);
     Assert.AreEqual(3, ev3.Recipients.Length);
     Assert.AreEqual(r1, ev3.Recipients[0]);
     Assert.AreEqual(r2, ev3.Recipients[1]);
     Assert.AreEqual(r3, ev3.Recipients[2]);
     DreamMessage ev3msg = ev3.AsMessage();
     Assert.AreEqual(msg, ev3msg.ToDocument());
     Assert.AreEqual(ev1.Id, ev3.Id);
     Assert.AreEqual("channel://foo.com/bar", ev3msg.Headers.DreamEventChannel);
     Assert.AreEqual("http://foo.com/baz", ev3msg.Headers.DreamEventOrigin[0]);
     string[] recipients = ev3msg.Headers.DreamEventRecipients;
     Assert.AreEqual(3, recipients.Length);
     Assert.AreEqual(r1.ToString(), recipients[0]);
     Assert.AreEqual(r2.ToString(), recipients[1]);
     Assert.AreEqual(r3.ToString(), recipients[2]);
 }
 /// <summary>
 /// Override hook for modifying the selection of event listeners based on recipients subscribed to the event.
 /// </summary>
 /// <param name="ev">Event to be dispatched.</param>
 /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
 /// <returns>Synchronization handle for the action's execution.</returns>
 protected virtual Yield GetListenersForRecipients(DispatcherEvent ev, Result <Dictionary <XUri, List <DispatcherRecipient> > > result)
 {
     //if the event has recipients attached, do subscription lookup by recipients
     _log.Debug("trying dispatch based on event recipient list event");
     lock (_destinationsByRecipient) {
         Dictionary <XUri, List <DispatcherRecipient> > listeners = new Dictionary <XUri, List <DispatcherRecipient> >();
         foreach (DispatcherRecipient recipient in ev.Recipients)
         {
             List <XUri> destinations;
             if (_destinationsByRecipient.TryGetValue(recipient, out destinations))
             {
                 foreach (XUri destination in destinations)
                 {
                     List <DispatcherRecipient> recipients;
                     if (!listeners.TryGetValue(destination, out recipients))
                     {
                         recipients = new List <DispatcherRecipient>();
                         listeners.Add(destination, recipients);
                     }
                     if (!recipients.Contains(recipient))
                     {
                         recipients.Add(recipient);
                     }
                 }
             }
         }
         result.Return(listeners);
     }
     yield break;
 }
        /// <summary>
        /// Override hook for modifying the selection of event listeners based on channel and resource matches.
        /// </summary>
        /// <param name="ev">Event to be dispatched.</param>
        /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
        /// <returns>Synchronization handle for the action's execution.</returns>
        protected virtual Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result <Dictionary <XUri, List <DispatcherRecipient> > > result)
        {
            // dispatch to all subscriptions that listen for this event and its contents
            _log.Debug("trying dispatch based on channel matches");
            ICollection <PubSubSubscription> listeningSubs = null;

            lock (_channelMap) {
                if (ev.Resource != null)
                {
                    listeningSubs = _resourceMap.GetMatches(ev.Resource);
                }
                listeningSubs = _channelMap.GetMatches(ev.Channel, listeningSubs);
            }
            Dictionary <XUri, List <DispatcherRecipient> > listeners = new Dictionary <XUri, List <DispatcherRecipient> >();

            foreach (PubSubSubscription sub in listeningSubs)
            {
                List <DispatcherRecipient> recipients;
                if (!listeners.TryGetValue(sub.Destination, out recipients))
                {
                    recipients = new List <DispatcherRecipient>();
                    listeners.Add(sub.Destination, recipients);
                }
                foreach (DispatcherRecipient recipient in sub.Recipients)
                {
                    if (!recipients.Contains(recipient))
                    {
                        recipients.Add(recipient);
                    }
                }
            }
            result.Return(listeners);
            yield break;
        }
 private DispatcherEvent(DispatcherEvent ev, XUri[] via, DispatcherRecipient[] recipients)
 {
     _message   = ev._message;
     Id         = ev.Id;
     Channel    = ev.Channel;
     Resource   = ev.Resource;
     Origins    = ev.Origins;
     Recipients = recipients;
     Via        = via;
 }
 /// <summary>
 /// Override hook for filtering recipients for an event.
 /// </summary>
 /// <param name="ev">Event to be dispatched.</param>
 /// <param name="recipients">List of proposed recipients.</param>
 /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
 /// <returns>Synchronization handle for the action's execution.</returns>
 protected virtual Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result <DispatcherEvent> result)
 {
     if (ev.Recipients.Length == 0)
     {
         //if the event has no reciepient list, anyone can receive it
         result.Return(ev);
         yield break;
     }
     recipients = ArrayUtil.Intersect(ev.Recipients, recipients);
     result.Return(recipients.Length == 0 ? null : ev.WithRecipient(true, recipients));
     yield break;
 }
 private void DispatchFromQueue(DispatcherEvent dispatchEvent, Action completionCallback)
 {
     Coroutine.Invoke(Dispatch_Helper, dispatchEvent, new Result()).WhenDone(
         r => {
         completionCallback();
         if (r.HasException)
         {
             _log.ErrorExceptionMethodCall(r.Exception, "AsyncDispatcher", "async queue processor encountered an error");
         }
         else
         {
             _log.DebugFormat("finished dispatch of event '{0}'", dispatchEvent.Id);
         }
     });
 }
        private void Update_Helper()
        {
            lock (_subscriptionsByOwner) {
                PubSubSubscription[] allSubs = CalculateCombinedSubscriptions();
                _combinedSetVersion++;
                _combinedSet = new PubSubSubscriptionSet(_owner, _combinedSetVersion, _serviceKeySetCookie, allSubs);
            }

            // this is outside the lock, since it may be an expensive operation and shouldn't block lookups
            DispatcherEvent ev = new DispatcherEvent(_combinedSet.AsDocument(), new XUri("pubsub:///set/update"), null, _owner);

            _log.DebugFormat("updated combined set, dispatching as id '{0}'", ev.Id);
            Dispatch(ev);
            if (CombinedSetUpdated != null)
            {
                CombinedSetUpdated(this, EventArgs.Empty);
            }
        }
 public void Can_roundtrip_DispatchItem()
 {
     var msg = new XDoc("msg");
     var channel = new XUri("channel://foo.com/bar");
     var origin = new XUri("http://foo.com/baz");
     var ev = new DispatcherEvent(msg, channel, origin);
     var item = new DispatchItem(
         new XUri("http://foo"),
         ev,
         "abc"
     );
     var serializer = new DispatchItemSerializer();
     var stream = serializer.ToStream(item);
     var item2 = serializer.FromStream(stream);
     Assert.AreEqual(item.Uri, item2.Uri, "uri mismatch");
     Assert.AreEqual(item.Location, item2.Location, "location mismatch");
     Assert.AreEqual(item.Event.Id, item2.Event.Id, "id mismatch");
 }
Exemple #10
0
 public void Add_via_to_event()
 {
     XDoc msg = new XDoc("msg");
     XUri via1 = new XUri("http://foo.com/route1");
     XUri via2 = new XUri("http://foo.com/route2");
     DispatcherEvent ev1 = new DispatcherEvent(msg, new XUri("channel://foo.com/bar"), new XUri("http://foo.com/baz"));
     DispatcherEvent ev2 = ev1.WithVia(via1);
     Assert.AreEqual(0, ev1.Via.Length);
     Assert.AreEqual(1, ev2.Via.Length);
     Assert.AreEqual(via1, ev2.Via[0]);
     DispatcherEvent ev3 = ev2.WithVia(via2);
     Assert.AreEqual(2, ev3.Via.Length);
     Assert.AreEqual(via1, ev3.Via[0]);
     Assert.AreEqual(via2, ev3.Via[1]);
     DreamMessage ev3msg = ev3.AsMessage();
     Assert.AreEqual(msg, ev3msg.ToDocument());
     Assert.AreEqual(ev1.Id, ev3.Id);
     Assert.AreEqual("channel://foo.com/bar", ev3msg.Headers.DreamEventChannel);
     Assert.AreEqual("http://foo.com/baz", ev3msg.Headers.DreamEventOrigin[0]);
     Assert.AreEqual(via1.ToString(), ev3msg.Headers.DreamEventVia[0]);
 }
        private Yield Dispatch_Helper(DispatcherEvent dispatchEvent, Result result)
        {
            Dictionary <XUri, List <DispatcherRecipient> > listeningEndpoints = null;

            if (dispatchEvent.Recipients != null && dispatchEvent.Recipients.Length > 0)
            {
                yield return(Coroutine.Invoke(GetListenersForRecipients, dispatchEvent, new Result <Dictionary <XUri, List <DispatcherRecipient> > >()).Set(v => listeningEndpoints = v));
            }
            else
            {
                yield return(Coroutine.Invoke(GetListenersByChannelResourceMatch, dispatchEvent, new Result <Dictionary <XUri, List <DispatcherRecipient> > >()).Set(v => listeningEndpoints = v));
            }
            if (listeningEndpoints.Count == 0)
            {
                _log.DebugFormat("event '{0}' for resource '{1}' has no endpoints to dispatch to", dispatchEvent.Id, dispatchEvent.Resource);
            }
            else
            {
                _log.DebugFormat("event '{0}' for resource '{1}' ready for dispatch with {2} endpoint(s)", dispatchEvent.Id, dispatchEvent.Resource, listeningEndpoints.Count);
            }
            foreach (KeyValuePair <XUri, List <DispatcherRecipient> > destination in listeningEndpoints)
            {
                var             uri      = destination.Key;
                DispatcherEvent subEvent = null;
                yield return(Coroutine.Invoke(DetermineRecipients, dispatchEvent, destination.Value.ToArray(), new Result <DispatcherEvent>()).Set(v => subEvent = v));

                if (subEvent == null)
                {
                    _log.DebugFormat("no recipient union for event '{0}' and {1}", dispatchEvent.Id, uri);
                    continue;
                }
                _log.DebugFormat("dispatching event '{0}' to {1}", subEvent.Id, uri);
                Plug p = Plug.New(uri);
                p = p.WithCookieJar(_cookieJar);
                Result <DreamMessage> response = p.Post(subEvent.AsMessage(), new Result <DreamMessage>(TimeSpan.MaxValue));
                response.WhenDone(r => DispatchCompletion_Helper(uri, r));
            }
            result.Return();
            yield break;
        }
        /// <summary>
        /// Dispatch an event against the registered subscriptions.
        /// </summary>
        /// <param name="ev">Dispatch event instance.</param>
        public void Dispatch(DispatcherEvent ev)
        {
            if (ev.HasVisited(_owner))
            {
                // this event is in a dispatch loop, so we drop it
                if (_log.IsWarnEnabled)
                {
                    _log.WarnFormat("event for channel '{0}' already visited the service, dropping", ev.Channel);
                    if (_log.IsDebugEnabled)
                    {
                        _log.Debug("  event origin:");
                        foreach (XUri origin in ev.Origins)
                        {
                            _log.DebugFormat("    - {0}", origin);
                        }
                        _log.Debug("  event route:");
                        foreach (XUri via in ev.Via)
                        {
                            _log.DebugFormat("    - {0}", via);
                        }
                    }
                }
                throw new DreamBadRequestException("Dispatch loop detected: The event has already been dispatched by this service");
            }
            if (_log.IsDebugEnabled)
            {
                _log.DebugFormat("Dispatcher '{0}' dispatching '{1}' on channel '{2}' with resource '{3}'",
                                 _owner,
                                 ev.Id,
                                 ev.Channel,
                                 ev.Resource);
            }
            DispatcherEvent dispatchEvent = ev.WithVia(_owner);

            if (!_dispatchQueue.TryEnqueue(dispatchEvent))
            {
                throw new InvalidOperationException(string.Format("Enqueue of '{0}' failed.", dispatchEvent.Id));
            }
        }
Exemple #13
0
        /// <summary>
        /// Override hook for filtering recipients for an event.
        /// </summary>
        /// <param name="ev">Event to be dispatched.</param>
        /// <param name="recipients">List of proposed recipients.</param>
        /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
        /// <returns>Synchronization handle for the action's execution.</returns>
        protected virtual Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result<DispatcherEvent> result)
        {
            if(ev.Recipients.Length == 0) {

                //if the event has no reciepient list, anyone can receive it
                result.Return(ev);
                yield break;
            }
            recipients = ArrayUtil.Intersect(ev.Recipients, recipients);
            result.Return(recipients.Length == 0 ? null : ev.WithRecipient(true, recipients));
            yield break;
        }
Exemple #14
0
 internal Yield PublishEvent(DreamContext context, DreamMessage request, Result<DreamMessage> response)
 {
     DispatcherEvent ev;
     try {
         ev = new DispatcherEvent(request);
         _log.DebugFormat("{0} received event '{1}'", this.Self.Uri, ev.Id);
         if(ev.Channel.Scheme == "pubsub") {
             response.Return(DreamMessage.Forbidden("events published into this service cannot be of scheme 'pubsub'"));
             yield break;
         }
         _dispatcher.Dispatch(ev);
         response.Return(DreamMessage.Ok(ev.GetEventEnvelope()));
     } catch(Exception e) {
         response.Return(DreamMessage.BadRequest(e.Message));
     }
     yield break;
 }
Exemple #15
0
        public void PubSub_end_to_end()
        {
            XUri testUri = _mockUri.At("foo", "sub1");
            string serviceKey = "1234";
            DreamCookie accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri);
            XDoc msg = new XDoc("foo");
            DispatcherEvent ev = new DispatcherEvent(
                msg,
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            XDoc set = new XDoc("subscription-set")
                .Elem("uri.owner", "http:///owner1")
                .Start("subscription")
                    .Attr("id", "1")
                    .Add(accessCookie.AsSetCookieDocument)
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Elem("uri", testUri).End()
                .End();

            // create subscription using a mockservice, so we get the general pubsub subscribe injected
            MockServiceInfo subMock = MockService.CreateMockService(_hostInfo);
            subMock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) {
                DreamMessage r = subMock.Service.PubSub.At("subscribers").PostAsync(set).Wait();
                Assert.IsTrue(r.IsSuccessful);
                Assert.AreEqual(DreamStatus.Created, r.Status);
                response2.Return(DreamMessage.Ok());
            };
            subMock.AtLocalHost.Post();

            // set up subscription destination mock
            DreamMessage receivedEvent = null;
            XUri recipient = null;
            ManualResetEvent resetEvent = new ManualResetEvent(false);
            MockPlug.Register(_mockUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response2) {
                _log.DebugFormat("destination called: {0}", uri);
                recipient = plug.Uri;
                receivedEvent = request;
                response2.Return(DreamMessage.Ok());
                resetEvent.Set();
            });

            // publish event via a mock service, so we get the general pubsub publish injected
            MockServiceInfo pubMock = MockService.CreateMockService(_hostInfo);
            pubMock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) {
                DreamMessage r = pubMock.Service.PubSub.At("publish").PostAsync(ev.AsMessage()).Wait();
                Assert.IsTrue(r.IsSuccessful);
                Assert.AreEqual(DreamStatus.Ok, r.Status);
                response2.Return(DreamMessage.Ok());
            };
            pubMock.AtLocalHost.Post();

            // wait for async dispatch to happen
            if(!resetEvent.WaitOne(1000, false)) {
                Assert.Fail("async dispatch didn't happen");
            }
            Assert.AreEqual(recipient, testUri);
            Assert.AreEqual(msg, receivedEvent.ToDocument());
            Assert.AreEqual(serviceKey, DreamCookie.GetCookie(receivedEvent.Cookies, "service-key").Value);
            Assert.AreEqual(ev.Id, receivedEvent.Headers.DreamEventId);
        }
Exemple #16
0
        public void Chained_pubSub_end_to_end()
        {
            XUri testUri = _mockUri.At("foo", "sub1");
            string serviceKey = "1234";
            DreamCookie accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri);
            XDoc msg = new XDoc("foo");
            DispatcherEvent ev = new DispatcherEvent(
                msg,
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            XDoc set = new XDoc("subscription-set")
                .Elem("uri.owner", "http:///owner1")
                .Start("subscription")
                    .Attr("id", "1")
                    .Add(accessCookie.AsSetCookieDocument)
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Elem("uri", testUri).End()
                .End();

            // create pubsub chain
            Plug middle = Plug.New(_hostInfo.Host.LocalMachineUri.At("host", "$pubsub", "subscribers"));
            Plug upstreamPubSub = CreatePubSubService("upstream", new XDoc("config").Start("downstream").Elem("uri", middle).End()).WithInternalKey().AtLocalHost;
            Plug downstreamPubSub = CreatePubSubService("downstream", new XDoc("config").Start("upstream").Elem("uri", middle).End()).WithInternalKey().AtLocalHost;

            // create subscription
            DreamMessage response = downstreamPubSub.At("subscribers").PostAsync(set).Wait();
            Assert.IsTrue(response.IsSuccessful);
            Assert.AreEqual(DreamStatus.Created, response.Status);

            // check that downstream really has the subscription
            Thread.Sleep(1000);
            DreamMessage downstreamSet = downstreamPubSub.At("subscribers").GetAsync().Wait();
            Assert.IsTrue(downstreamSet.IsSuccessful);
            XDoc downstreamSetDoc = downstreamSet.ToDocument();
            Assert.AreEqual(testUri.ToString(), downstreamSetDoc["subscription/recipient/uri"].AsText);

            // check that upstream really has the subscription
            DreamMessage upstreamSet = downstreamPubSub.At("subscribers").GetAsync().Wait();
            Assert.IsTrue(upstreamSet.IsSuccessful);
            XDoc upstreamSetDoc = upstreamSet.ToDocument();
            Assert.AreEqual(testUri.ToString(), upstreamSetDoc["subscription/recipient/uri"].AsText);

            // set up subscription destination mock
            DreamMessage receivedEvent = null;
            XDoc receivedDoc = null;
            XUri recipient = null;
            ManualResetEvent resetEvent = new ManualResetEvent(false);
            MockPlug.Register(_mockUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response2) {
                recipient = plug.Uri;
                receivedEvent = request;
                receivedDoc = request.ToDocument();
                response2.Return(DreamMessage.Ok());
                resetEvent.Set();
            });

            // publish event via a mock service, since publish is marked internal
            _log.DebugFormat("setting up Mock Service");
            DreamCookie upstreamCookie = upstreamPubSub.CookieJar.Fetch(upstreamPubSub.Uri)[0];
            XDoc upstreamSetCookieElement = upstreamCookie.AsSetCookieDocument;
            MockServiceInfo mock = MockService.CreateMockService(
                _hostInfo,
                new XDoc("config")
                .Elem("uri.pubsub", upstreamPubSub.Uri)
                .Add(upstreamSetCookieElement));
            mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) {
                _log.DebugFormat("publishing event (in MockService)");
                DreamMessage r = mock.Service.PubSub.At("publish").PostAsync(ev.AsMessage()).Wait();
                Assert.IsTrue(r.IsSuccessful);
                Assert.AreEqual(DreamStatus.Ok, r.Status);
                response2.Return(DreamMessage.Ok());
            };
            _log.DebugFormat("publishing event via up Mock Service");
            mock.AtLocalHost.Post();

            // wait for async dispatch to happen
            _log.DebugFormat("waiting on async dispatch");
            if(!resetEvent.WaitOne((int)TimeSpan.FromSeconds(1).TotalMilliseconds, true)) {
                Assert.Fail("async dispatch didn't happen");
            }
            Assert.AreEqual(recipient, testUri);
            Assert.AreEqual(msg, receivedDoc);
            Assert.AreEqual(serviceKey, DreamCookie.GetCookie(receivedEvent.Cookies, "service-key").Value);
            Assert.AreEqual(ev.Id, receivedEvent.Headers.DreamEventId);
        }
Exemple #17
0
        protected override Yield FilterRecipients(DispatcherEvent ev, PubSubSubscription subscription, Result<DispatcherEvent> result) {
            var recipients2 = new List<DispatcherRecipient>();
            uint? pageid = null;
            string wikiId = null;
            if(ev.HasDocument) {
                var changeDoc = ev.AsDocument();
                pageid = changeDoc["pageid"].AsUInt;
                wikiId = changeDoc["@wikiid"].AsText;
            }
            var userIds = new Dictionary<int, DispatcherRecipient>();
            foreach(var recipient in subscription.Recipients) {
                var authtoken = recipient.Doc["@authtoken"].AsText;
                if(string.IsNullOrEmpty(authtoken)) {

                    // if the recipient has no authtoken, but has a userid, collect the Id so we can authorize it against the page
                    int? userId = recipient.Doc["@userid"].AsInt;
                    if(userId.HasValue) {
                        userIds.Add(userId.Value, recipient);
                    }
                } else if(authtoken == _authtoken) {

                    // master authtoken means the recipient doesn't need page level authorization (such as lucene)
                    recipients2.Add(recipient);
                } else if(!string.IsNullOrEmpty(wikiId)) {
                    var key = authtoken + ":" + wikiId;
                    if(!_validatedKeys.Contains(key)) {

                        // no valid key found, need to check with API to validate
                        XDoc settings = null;
                        yield return _deki.At("site", "settings")
                            .With("apikey", _authtoken)
                            .WithHeader("X-Deki-Site", "id=" + wikiId)
                            .Get(new Result<DreamMessage>())
                            .Set(x => settings = x.IsSuccessful ? x.ToDocument() : null);
                        if(settings == null || !authtoken.EqualsInvariant(settings["security/api-key"].AsText)) {
                            continue;
                        }
                        _validatedKeys.Add(key);
                    }

                    // instance authtoken means the recipient doesn't need page level authorization (such as lucene)
                    recipients2.Add(recipient);
                }
            }
            if(userIds.Count > 0 && (ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete"))) {

                // check all userId's against the page to prune set to authorized users
                var users = new XDoc("users");
                foreach(int userid in userIds.Keys) {
                    users.Start("user").Attr("id", userid).End();
                }
                if(pageid.HasValue) {
                    Result<DreamMessage> userAuthResult;
                    yield return userAuthResult = _deki.At("pages", pageid.Value.ToString(), "allowed")
                        .With("permissions", "read,subscribe")
                        .With("filterdisabled", true)
                        .WithHeader("X-Deki-Site", "id=" + wikiId)
                        .PostAsync(users);
                    DreamMessage userAuth = userAuthResult.Value;
                    if(userAuth.IsSuccessful) {
                        int authorized = 0;
                        foreach(XDoc userid in userAuth.ToDocument()["user/@id"]) {
                            DispatcherRecipient recipient;
                            if(!userIds.TryGetValue(userid.AsInt.GetValueOrDefault(), out recipient)) {
                                continue;
                            }
                            authorized++;
                            recipients2.Add(recipient);
                        }
                        if(authorized != userIds.Count) {
                            _log.DebugFormat("requested auth on {0} users, received auth on {1} for page {2}", userIds.Count, authorized, pageid.Value);
                        }
                    } else {
                        _log.WarnFormat("unable to retrieve user auth for page '{0}': {1}", pageid, userAuth.Status);
                    }
                }
            }
            result.Return(recipients2.Count == 0 ? null : ev.WithRecipient(true, recipients2.ToArray()));
            yield break;
        }
Exemple #18
0
        private void Update_Helper()
        {
            lock(_subscriptionsByOwner) {
                PubSubSubscription[] allSubs = CalculateCombinedSubscriptions();
                _combinedSetVersion++;
                _combinedSet = new PubSubSubscriptionSet(_owner, _combinedSetVersion, _serviceKeySetCookie, allSubs);
            }

            // this is outside the lock, since it may be an expensive operation and shouldn't block lookups
            DispatcherEvent ev = new DispatcherEvent(_combinedSet.AsDocument(), new XUri("pubsub:///set/update"), null, _owner);
            _log.DebugFormat("updated combined set, dispatching as id '{0}'", ev.Id);
            Dispatch(ev);
            if(CombinedSetUpdated != null) {
                CombinedSetUpdated(this, EventArgs.Empty);
            }
        }
Exemple #19
0
 private void DispatchFromQueue(DispatcherEvent dispatchEvent, Action completionCallback)
 {
     Coroutine.Invoke(Dispatch_Helper, dispatchEvent, new Result()).WhenDone(
         r => {
             completionCallback();
             if(r.HasException) {
                 _log.ErrorExceptionMethodCall(r.Exception, "AsyncDispatcher", "async queue processor encountered an error");
             } else {
                 _log.DebugFormat("finished dispatch of event '{0}'", dispatchEvent.Id);
             }
         });
 }
Exemple #20
0
        public void Dispatch_based_on_channel_match_with_different_wikiid_patterns_but_same_proxy_destination()
        {
            DispatcherEvent ev = new DispatcherEvent(
                new XDoc("msg"),
                new XUri("event://sales.mindtouch.com/deki/comments/create"),
                new XUri("http://foobar.com/some/comment"));
            List<DreamMessage> dispatches = new List<DreamMessage>();
            XUri testUri = new XUri("http://sales.mindtouch.com/").At(StringUtil.CreateAlphaNumericKey(4));
            AutoResetEvent resetEvent = new AutoResetEvent(false);
            int dispatchCounter = 0;
            int expectedDispatches = 0;
            MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) {
                if(testUri == plug.Uri) {
                    dispatches.Add(request);
                    dispatchCounter++;
                    // ReSharper disable AccessToModifiedClosure
                    if(dispatchCounter >= expectedDispatches) {
                        // ReSharper restore AccessToModifiedClosure
                        resetEvent.Set();
                    }
                }
                response.Return(DreamMessage.Ok());
            });
            Plug owner = Plug.New("mock:///pubsub");
            DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie });
            int expectedCombinedSetUpdates = 2;
            int combinedSetUpdates = 0;
            AutoResetEvent setResetEvent = new AutoResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                combinedSetUpdates++;
                _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates);
                if(combinedSetUpdates >= expectedCombinedSetUpdates) {
                    setResetEvent.Set();
                }
            };
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner1")
                    .Start("subscription")
                        .Attr("id", "1")
                        .Elem("channel", "event://sales.mindtouch.com/deki/comments/create")
                        .Elem("channel", "event://sales.mindtouch.com/deki/comments/update")
                        .Elem("uri.proxy", testUri)
                        .Start("recipient").Elem("uri", testUri.At("sub1")).End()
                    .End());
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner2")
                    .Start("subscription")
                        .Attr("id", "3")
                        .Elem("channel", "event://*/deki/comments/create")
                        .Elem("channel", "event://*/deki/comments/update")
                        .Elem("uri.proxy", testUri)
                        .Start("recipient").Elem("uri", testUri.At("sub2")).End()
                    .End());

            // combinedset updates happen asynchronously, so give'em a chance
            Assert.IsTrue(setResetEvent.WaitOne(10000, false));
            expectedDispatches = 1;
            dispatcher.Dispatch(ev);

            // dispatch happens async on a worker thread
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Assert.AreEqual(1, dispatches.Count);
            Assert.AreEqual(ev.AsMessage().ToDocument(), dispatches[0].ToDocument());
            Assert.AreEqual(ev.Id, dispatches[0].Headers.DreamEventId);
            MockPlug.Deregister(testUri);
        }
 public void Deserialize_wrong_version_throws()
 {
     var msg = new XDoc("msg");
     var channel = new XUri("channel://foo.com/bar");
     var origin = new XUri("http://foo.com/baz");
     var ev = new DispatcherEvent(msg, channel, origin);
     var item = new DispatchItem(
         new XUri("http://foo"),
         ev,
         "abc"
     );
     var serializer = new DispatchItemSerializer();
     var stream = serializer.ToStream(item);
     stream.WriteByte(5);
     stream.Position = 0;
     try {
         serializer.FromStream(stream);
         Assert.Fail("should have thrown");
     } catch(InvalidDataException) {
         return;
     }
 }
Exemple #22
0
 public void Dispatch(DispatcherEvent ev)
 {
     throw new NotImplementedException();
 }
Exemple #23
0
        public void Dispatch_based_on_recipients()
        {
            int workers;
            int io;
            ThreadPool.GetAvailableThreads(out workers, out io);
            _log.DebugFormat("threadpool threads: {0}/{1}", workers, io);
            string proxyRecipient1 = "mailto:///[email protected]";
            string proxyRecipient2 = "mailto:///[email protected]";
            XDoc msg = new XDoc("foo");
            DispatcherEvent ev = new DispatcherEvent(
                msg,
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"))
                .WithRecipient(false,
                    new DispatcherRecipient(new XUri(proxyRecipient1)),
                    new DispatcherRecipient(new XUri("mailto:///[email protected]")),
                    new DispatcherRecipient(new XUri(proxyRecipient2)));
            Dictionary<XUri, DreamMessage> dispatches = new Dictionary<XUri, DreamMessage>();
            XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4));
            AutoResetEvent resetEvent = new AutoResetEvent(false);
            MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) {
                dispatches.Add(plug.Uri, request);
                resetEvent.Set();
                response.Return(DreamMessage.Ok());
            });
            Plug owner = Plug.New("mock:///pubsub");
            DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie });
            AutoResetEvent setResetEvent = new AutoResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                _log.DebugFormat("set updated");
                setResetEvent.Set();
            };

            XUri proxy = testUri.At("proxy");
            _log.DebugFormat("registering set");
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner1")
                    .Start("subscription")
                        .Attr("id", "1")
                        .Elem("channel", "channel:///foo/*")
                        .Elem("uri.proxy", proxy)
                        .Start("recipient").Elem("uri", proxyRecipient1).End()
                        .Start("recipient").Elem("uri", proxyRecipient2).End()
                    .End()
                    .Start("subscription")
                        .Attr("id", "2")
                        .Elem("channel", "channel:///foo/*")
                        .Start("recipient").Elem("uri", testUri.At("sub2")).End()
                    .End());

            //Set updates happen asynchronously, so give it a chance
            _log.DebugFormat("giving registration a chance to manifest");
            Assert.IsTrue(setResetEvent.WaitOne(10000, false));
            _log.DebugFormat("dispatching event");
            dispatcher.Dispatch(ev);

            // dispatch happens async on a worker thread
            Assert.IsTrue(resetEvent.WaitOne(1000, false));
            Thread.Sleep(200);
            Assert.AreEqual(1, dispatches.Count);
            Assert.IsTrue(dispatches.ContainsKey(proxy));
            Assert.AreEqual(msg, dispatches[proxy].ToDocument());
            Assert.AreEqual(ev.Id, dispatches[proxy].Headers.DreamEventId);
            string[] recipients = dispatches[proxy].Headers.DreamEventRecipients;
            Assert.AreEqual(2, recipients.Length);
            Assert.Contains(proxyRecipient1, recipients);
            Assert.Contains(proxyRecipient2, recipients);
            MockPlug.Deregister(testUri);
        }
 public void Speed()
 {
     var body = new XDoc("msg").Elem("foo", "bar");
     var channel = new XUri("channel://foo.com/bar");
     var resource = new XUri("http://foo.com/baz/0");
     var origin1 = new XUri("http://foo.com/baz/1");
     var origin2 = new XUri("http://foo.com/baz/2");
     var recipient1 = new DispatcherRecipient(new XUri("http://recipient1"));
     var recipient2 = new DispatcherRecipient(new XUri("http://recipient2"));
     var via1 = new XUri("http://via1");
     var via2 = new XUri("http://via2");
     var ev = new DispatcherEvent(body, channel, resource, origin1, origin2);
     ev = ev.WithRecipient(false, recipient1, recipient2).WithVia(via1).WithVia(via2);
     var item = new DispatchItem(
         new XUri("http://foo"),
         ev,
         "abc"
     );
     var serializer = new DispatchItemSerializer();
     Stream stream = null;
     var n = 100000;
     var t = Stopwatch.StartNew();
     for(var i = 0; i < n; i++) {
         stream = serializer.ToStream(item);
     }
     t.Stop();
     Console.WriteLine("serialize {0:0} items/sec", n / t.Elapsed.TotalSeconds);
     t = Stopwatch.StartNew();
     for(var i = 0; i < n; i++) {
         serializer.FromStream(stream);
         stream.Seek(0, SeekOrigin.Begin);
     }
     t.Stop();
     Console.WriteLine("deserialize {0:0} items/sec", n / t.Elapsed.TotalSeconds);
 }
Exemple #25
0
 /// <summary>
 /// Override hook for modifying the selection of event listeners based on channel and resource matches.
 /// </summary>
 /// <param name="ev">Event to be dispatched.</param>
 /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
 /// <returns>Synchronization handle for the action's execution.</returns>
 protected virtual Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result<Dictionary<XUri, List<DispatcherRecipient>>> result)
 {
     // dispatch to all subscriptions that listen for this event and its contents
     _log.Debug("trying dispatch based on channel matches");
     ICollection<PubSubSubscription> listeningSubs = null;
     lock(_channelMap) {
         if(ev.Resource != null) {
             listeningSubs = _resourceMap.GetMatches(ev.Resource);
         }
         listeningSubs = _channelMap.GetMatches(ev.Channel, listeningSubs);
     }
     Dictionary<XUri, List<DispatcherRecipient>> listeners = new Dictionary<XUri, List<DispatcherRecipient>>();
     foreach(PubSubSubscription sub in listeningSubs) {
         List<DispatcherRecipient> recipients;
         if(!listeners.TryGetValue(sub.Destination, out recipients)) {
             recipients = new List<DispatcherRecipient>();
             listeners.Add(sub.Destination, recipients);
         }
         foreach(DispatcherRecipient recipient in sub.Recipients) {
             if(!recipients.Contains(recipient)) {
                 recipients.Add(recipient);
             }
         }
     }
     result.Return(listeners);
     yield break;
 }
Exemple #26
0
        public void Dispatch_based_on_channel_and_resource_match()
        {
            DispatcherEvent ev = new DispatcherEvent(
                new XDoc("msg"),
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            Dictionary<XUri, DreamMessage> dispatches = new Dictionary<XUri, DreamMessage>();
            XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4));
            AutoResetEvent resetEvent = new AutoResetEvent(false);
            int expectedDispatches = 0;
            MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) {
                dispatches.Add(plug.Uri, request);
                // ReSharper disable AccessToModifiedClosure
                if(dispatches.Count >= expectedDispatches) {
                    // ReSharper restore AccessToModifiedClosure
                    resetEvent.Set();
                }
                response.Return(DreamMessage.Ok());
            });
            Plug owner = Plug.New("mock:///pubsub");
            DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie });
            int expectedCombinedSetUpdates = 2;
            int combinedSetUpdates = 0;
            AutoResetEvent setResetEvent = new AutoResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                combinedSetUpdates++;
                _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates);
                if(combinedSetUpdates >= expectedCombinedSetUpdates) {
                    setResetEvent.Set();
                }
            };
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner1")
                    .Start("subscription")
                        .Attr("id", "1")
                        .Elem("channel", "channel:///foo/*")
                        .Elem("uri.resource", "http://*/some/*")
                        .Start("recipient").Elem("uri", testUri.At("sub1")).End()
                    .End()
                    .Start("subscription")
                        .Attr("id", "2")
                        .Elem("channel", "channel:///foo/baz")
                        .Elem("uri.resource", "http://*/some/*")
                        .Start("recipient").Elem("uri", testUri.At("sub2")).End()
                    .End());
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner2")
                    .Start("subscription")
                        .Attr("id", "3")
                        .Elem("channel", "channel:///foo/bar")
                        .Elem("uri.resource", "http://foobar.com/some/page")
                        .Start("recipient").Elem("uri", testUri.At("sub3")).End()
                    .End()
                    .Start("subscription")
                        .Attr("id", "4")
                        .Elem("channel", "channel:///foo/bar")
                        .Elem("uri.resource", "http://baz.com/some/*")
                        .Start("recipient").Elem("uri", testUri.At("sub4")).End()
                    .End());

            // combinedset updates happen asynchronously, so give'em a chance
            Assert.IsTrue(setResetEvent.WaitOne(10000, false));
            expectedDispatches = 2;
            dispatcher.Dispatch(ev);

            // dispatch happens async on a worker thread
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Thread.Sleep(200);
            Assert.AreEqual(2, dispatches.Count);
            Assert.IsTrue(dispatches.ContainsKey(testUri.At("sub1")));
            Assert.AreEqual(ev.AsMessage().ToDocument(), dispatches[testUri.At("sub1")].ToDocument());
            Assert.AreEqual(ev.Id, dispatches[testUri.At("sub1")].Headers.DreamEventId);
            Assert.IsTrue(dispatches.ContainsKey(testUri.At("sub3")));
            MockPlug.Deregister(testUri);
        }
Exemple #27
0
 /// <summary>
 /// Override hook for modifying the selection of event listeners based on recipients subscribed to the event.
 /// </summary>
 /// <param name="ev">Event to be dispatched.</param>
 /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param>
 /// <returns>Synchronization handle for the action's execution.</returns>
 protected virtual Yield GetListenersForRecipients(DispatcherEvent ev, Result<Dictionary<XUri, List<DispatcherRecipient>>> result)
 {
     //if the event has recipients attached, do subscription lookup by recipients
     _log.Debug("trying dispatch based on event recipient list event");
     lock(_destinationsByRecipient) {
         Dictionary<XUri, List<DispatcherRecipient>> listeners = new Dictionary<XUri, List<DispatcherRecipient>>();
         foreach(DispatcherRecipient recipient in ev.Recipients) {
             List<XUri> destinations;
             if(_destinationsByRecipient.TryGetValue(recipient, out destinations)) {
                 foreach(XUri destination in destinations) {
                     List<DispatcherRecipient> recipients;
                     if(!listeners.TryGetValue(destination, out recipients)) {
                         recipients = new List<DispatcherRecipient>();
                         listeners.Add(destination, recipients);
                     }
                     if(!recipients.Contains(recipient)) {
                         recipients.Add(recipient);
                     }
                 }
             }
         }
         result.Return(listeners);
     }
     yield break;
 }
Exemple #28
0
        public void Repeated_dispatch_failure_kicks_subscription_set()
        {
            // TODO (steveb): test fails under Mono 2.8.2

            var sub1Uri = new XUri("http://sub1/foo");
            var sub1Mock = MockPlug.Register(sub1Uri);
            sub1Mock.Expect().Verb("POST").Response(DreamMessage.BadRequest("nobody home"));
            var sub2Uri = new XUri("http://sub2/foo");
            var sub2Mock = MockPlug.Register(sub2Uri);
            sub2Mock.Expect().Verb("POST").Response(DreamMessage.BadRequest("nobody home"));
            var ev = new DispatcherEvent(
                new XDoc("msg"),
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            var cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            var dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = Plug.New("mock:///pubsub"), ServiceAccessCookie = cookie });
            var expectedCombinedSetUpdates = 2;
            var combinedSetUpdates = 0;
            var setResetEvent = new ManualResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                combinedSetUpdates++;
                _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates);
                if(combinedSetUpdates >= expectedCombinedSetUpdates) {
                    setResetEvent.Set();
                }
            };
            var location1 = dispatcher.RegisterSet(new XDoc("subscription-set")
                .Attr("max-failures", 0)
                .Elem("uri.owner", "http:///owner1")
                .Start("subscription")
                    .Attr("id", "1")
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Elem("uri", sub1Uri).End()
                .End()).Item1.Location;
            var location2 = dispatcher.RegisterSet(new XDoc("subscription-set")
                .Attr("max-failures", 0)
                .Elem("uri.owner", "http:///owner2")
                .Start("subscription")
                    .Attr("id", "1")
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Elem("uri", sub2Uri).End()
                .End()).Item1.Location;
            Assert.IsTrue(setResetEvent.WaitOne(10000, false), "combined set didn't change expected number of times");
            Assert.IsNotNull(dispatcher[location1]);
            Assert.IsNotNull(dispatcher[location2]);
            dispatcher.Dispatch(ev);
            Assert.IsTrue(sub1Mock.WaitAndVerify(TimeSpan.FromSeconds(10)), sub1Mock.VerificationFailure);
            Assert.IsTrue(sub2Mock.WaitAndVerify(TimeSpan.FromSeconds(10)), sub1Mock.VerificationFailure);
            Assert.IsTrue(Wait.For(() => dispatcher[location2] == null, TimeSpan.FromSeconds(10)), "Second set wasn't kicked");
            Assert.IsTrue(Wait.For(() => dispatcher[location1] == null, TimeSpan.FromSeconds(10)), "First set wasn't kicked");
        }
Exemple #29
0
 private Yield Dispatch_Helper(DispatcherEvent dispatchEvent, Result result)
 {
     Dictionary<XUri, List<DispatcherRecipient>> listeningEndpoints = null;
     if(dispatchEvent.Recipients != null && dispatchEvent.Recipients.Length > 0) {
         yield return Coroutine.Invoke(GetListenersForRecipients, dispatchEvent, new Result<Dictionary<XUri, List<DispatcherRecipient>>>()).Set(v => listeningEndpoints = v);
     } else {
         yield return Coroutine.Invoke(GetListenersByChannelResourceMatch, dispatchEvent, new Result<Dictionary<XUri, List<DispatcherRecipient>>>()).Set(v => listeningEndpoints = v);
     }
     if(listeningEndpoints.Count == 0) {
         _log.DebugFormat("event '{0}' for resource '{1}' has no endpoints to dispatch to", dispatchEvent.Id, dispatchEvent.Resource);
     } else {
         _log.DebugFormat("event '{0}' for resource '{1}' ready for dispatch with {2} endpoint(s)", dispatchEvent.Id, dispatchEvent.Resource, listeningEndpoints.Count);
     }
     foreach(KeyValuePair<XUri, List<DispatcherRecipient>> destination in listeningEndpoints) {
         var uri = destination.Key;
         DispatcherEvent subEvent = null;
         yield return Coroutine.Invoke(DetermineRecipients, dispatchEvent, destination.Value.ToArray(), new Result<DispatcherEvent>()).Set(v => subEvent = v);
         if(subEvent == null) {
             _log.DebugFormat("no recipient union for event '{0}' and {1}", dispatchEvent.Id, uri);
             continue;
         }
         _log.DebugFormat("dispatching event '{0}' to {1}", subEvent.Id, uri);
         Plug p = Plug.New(uri);
         p = p.WithCookieJar(_cookieJar);
         Result<DreamMessage> response = p.Post(subEvent.AsMessage(), new Result<DreamMessage>(TimeSpan.MaxValue));
         response.WhenDone(r => DispatchCompletion_Helper(uri, r));
     }
     result.Return();
     yield break;
 }
Exemple #30
0
        public void New_Event_from_XDoc_and_back()
        {
            XDoc msg = new XDoc("msg");
            XUri channel = new XUri("channel://foo.com/bar");
            XUri origin = new XUri("http://foo.com/baz");
            DispatcherEvent ev = new DispatcherEvent(msg, channel, origin);
            Assert.IsFalse(string.IsNullOrEmpty(ev.Id));
            Assert.AreEqual(channel, ev.Channel);
            List<XUri> origins = new List<XUri>(ev.Origins);
            Assert.AreEqual(1, origins.Count);
            Assert.AreEqual(origin, origins[0]);

            DreamMessage message = ev.AsMessage();
            Assert.AreEqual(msg, message.ToDocument());
            Assert.AreEqual(channel.ToString(), message.Headers.DreamEventChannel);
            Assert.AreEqual(origin.ToString(), message.Headers.DreamEventOrigin[0]);
        }
        protected override Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result<DispatcherEvent> result) {
            List<DispatcherRecipient> recipients2 = new List<DispatcherRecipient>();

            Dictionary<int, DispatcherRecipient> userIds = new Dictionary<int, DispatcherRecipient>();
            foreach(DispatcherRecipient r in recipients) {
                string authtoken = r.Doc["@authtoken"].AsText;
                if(string.IsNullOrEmpty(authtoken)) {

                    // if the reciepient has no authtoken, but has a userid, collect the Id so we can authorize it against the page
                    int? userId = r.Doc["@userid"].AsInt;
                    if(userId.HasValue) {
                        userIds.Add(userId.Value, r);
                    }
                } else if(authtoken == _authtoken) {

                    // authtoken means the recipient doesn't need page level authorization (such as lucene)
                    recipients2.Add(r);
                }
            }
            if(userIds.Count > 0 && (ev.Channel.Segments.Length <= 2 || !StringUtil.EqualsInvariantIgnoreCase(ev.Channel.Segments[2], "delete"))) {

                // check all userId's against the page to prune set to authorized users
                XDoc users = new XDoc("users");
                foreach(int userid in userIds.Keys) {
                    users.Start("user").Attr("id", userid).End();
                }
                XDoc changeDoc = ev.AsDocument();
                uint? pageid = changeDoc["pageid"].AsUInt;
                string wikiId = changeDoc["@wikiid"].AsText;
                if(pageid.HasValue) {
                    Result<DreamMessage> userAuthResult;
                    yield return userAuthResult = _deki.At("pages", pageid.Value.ToString(), "allowed")
                        .With("permissions", "read,subscribe")
                        .With("filterdisabled", true)
                        .WithHeader("X-Deki-Site", "id=" + wikiId)
                        .PostAsync(users);
                    DreamMessage userAuth = userAuthResult.Value;
                    if(userAuth.IsSuccessful) {
                        int authorized = 0;
                        foreach(XDoc userid in userAuth.ToDocument()["user/@id"]) {
                            DispatcherRecipient recipient;
                            if(!userIds.TryGetValue(userid.AsInt.GetValueOrDefault(), out recipient)) {
                                continue;
                            }
                            authorized++;
                            recipients2.Add(recipient);
                        }
                        if(authorized != userIds.Count) {
                            _log.DebugFormat("requested auth on {0} users, received auth on {1} for page {2}", userIds.Count, authorized, pageid.Value);
                        }
                    } else {
                        _log.WarnFormat("unable to retrieve user auth for page '{0}': {1}", pageid, userAuth.Status);
                    }
                }
            }
            result.Return(recipients2.Count == 0 ? null : ev.WithRecipient(true, recipients2.ToArray()));
            yield break;
        }
Exemple #32
0
 public void New_Event_from_bytes_and_back_as_multiple_streams()
 {
     byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
     DispatcherEvent ev = new DispatcherEvent(DreamMessage.Ok(MimeType.BINARY, bytes), new XUri("channel:///foo"), new XUri("http:///origin"));
     DreamMessage m1 = ev.AsMessage();
     DreamMessage m2 = ev.AsMessage();
     Assert.AreEqual(9, m1.ContentLength);
     Assert.AreEqual(9, m2.ContentLength);
     MemoryStream ms1 = m1.ToStream().ToMemoryStream(m1.ContentLength, new Result<MemoryStream>()).Wait();
     MemoryStream ms2 = m1.ToStream().ToMemoryStream(m1.ContentLength, new Result<MemoryStream>()).Wait();
     Assert.AreEqual(bytes, ms1.GetBuffer());
     Assert.AreEqual(bytes, ms2.GetBuffer());
 }
Exemple #33
0
        protected override Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result<Dictionary<XUri, List<PubSubSubscription>>> result) {
            if(!ev.Channel.Segments[1].EqualsInvariantIgnoreCase("pages")) {

                // not a page DispatcherEvent or a page delete DispatcherEvent, use default matcher
                Result<Dictionary<XUri, List<PubSubSubscription>>> baseResult;
                yield return baseResult = Coroutine.Invoke(base.GetListenersByChannelResourceMatch, ev, new Result<Dictionary<XUri, List<PubSubSubscription>>>());
                result.Return(baseResult);
                yield break;
            }
            var matches = new List<PubSubSubscription>();
            if(ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete")) {

                // dispatch to all PubSubSubscriptions that listen for this DispatcherEvent and its contents
                XDoc evDoc = ev.AsDocument();
                uint? pageid = evDoc["pageid"].AsUInt;
                string wikiId = evDoc["@wikiid"].AsText;
                bool first = true;
                _log.DebugFormat("trying dispatch based on channel & page PubSubSubscriptions for page '{0}' from wiki '{1}'", pageid, wikiId);

                // fetch parent page id's for this page so that we can resolve infinite depth PubSubSubscriptions
                Result<DreamMessage> pageHierarchyResult;
                yield return pageHierarchyResult = _deki.At("pages", pageid.ToString()).WithHeader("X-Deki-Site", "id=" + wikiId).GetAsync();
                DreamMessage pageHierarchy = pageHierarchyResult.Value;
                if(pageHierarchy.IsSuccessful) {
                    XDoc pageDoc = pageHierarchy.ToDocument();
                    while(pageid.HasValue) {
                        List<Tuplet<PubSubSubscription, bool>> subs;
                        _subscriptionsByPage.TryGetValue(pageid.Value, out subs);
                        if(subs != null) {

                            // only the first pageId (the one from the event) triggers on non-infinite depth subs
                            foreach(var sub in subs) {
                                if((sub.Item2 || first) && !matches.Contains(sub.Item1)) {
                                    matches.Add(sub.Item1);
                                }
                            }
                        }

                        // get parent id and then set pageDoc to the parent's subdoc, so we can descend the ancesstor tree further
                        pageid = pageDoc["page.parent/@id"].AsUInt;
                        pageDoc = pageDoc["page.parent"];
                        first = false;
                    }
                } else {
                    _log.WarnFormat("unable to retrieve page doc for page '{0}': {1}", pageid, pageHierarchy.Status);
                }
            }
            ICollection<PubSubSubscription> listeningSubs;
            lock(_channelMap) {

                // get all the PubSubSubscriptions that are wild card matches (which is basically those that didn't
                // have any resources in their PubSubSubscription) and add them to the above matches
                foreach(var sub in _resourceMap.GetMatches(new XUri("http://dummy/dummy"))) {
                    if(!matches.Contains(sub)) {
                        matches.Add(sub);
                    }
                }
                listeningSubs = _channelMap.GetMatches(ev.Channel, matches);
            }
            var listeners = new Dictionary<XUri, List<PubSubSubscription>>();
            foreach(var sub in listeningSubs) {
                List<PubSubSubscription> subs;
                if(!listeners.TryGetValue(sub.Destination, out subs)) {
                    subs = new List<PubSubSubscription>();
                    listeners.Add(sub.Destination, subs);
                    subs.Add(sub);
                } else if(!subs.Contains(sub)) {
                    subs.Add(sub);
                }
            }
            result.Return(listeners);
            yield break;
        }
Exemple #34
0
        public void Failed_dispatch_followed_by_success_should_reset_fail_count()
        {
            bool fail = true;
            DispatcherEvent ev = new DispatcherEvent(
             new XDoc("msg"),
             new XUri("channel:///foo/bar"),
             new XUri("http://foobar.com/some/page"));
            XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4));
            AutoResetEvent resetEvent = new AutoResetEvent(false);
            int mockCalled = 0;
            MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) {
                mockCalled++;
                // ReSharper disable AccessToModifiedClosure
                _log.DebugFormat("mock called {0} times (fail={1}): {2}", mockCalled, fail, uri);
                // ReSharper restore AccessToModifiedClosure
                resetEvent.Set();
                // ReSharper disable AccessToModifiedClosure
                response.Return(fail ? DreamMessage.InternalError() : DreamMessage.Ok());
                // ReSharper restore AccessToModifiedClosure
            });
            DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = Plug.New("mock:///pubsub"), ServiceAccessCookie = cookie });
            int expectedCombinedSetUpdates = 1;
            int combinedSetUpdates = 0;
            AutoResetEvent setResetEvent = new AutoResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                combinedSetUpdates++;
                _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates);
                if(combinedSetUpdates >= expectedCombinedSetUpdates) {
                    setResetEvent.Set();
                }
            };
            string location = dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Attr("max-failures", 1)
                    .Elem("uri.owner", "http:///owner1")
                    .Start("subscription")
                        .Attr("id", "1")
                        .Elem("channel", "channel:///foo/*")
                        .Start("recipient").Elem("uri", testUri.At("foo")).End()
                    .End()).Item1.Location;
            Assert.IsTrue(setResetEvent.WaitOne(10000, false));
            Assert.IsNotNull(dispatcher[location]);

            _log.DebugFormat("first dispatch (fail={0})", fail);
            dispatcher.Dispatch(ev);
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Thread.Sleep(1000); // failure gets dealt with async
            Assert.IsNotNull(dispatcher[location]);
            fail = false;

            _log.DebugFormat("second dispatch (fail={0})", fail);
            dispatcher.Dispatch(ev);
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Thread.Sleep(1000); // failure reset gets dealt with async
            Assert.IsNotNull(dispatcher[location]);
            fail = true;

            _log.DebugFormat("third dispatch (fail={0})", fail);
            dispatcher.Dispatch(ev);
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Thread.Sleep(1000); // failure gets dealt with async
            Assert.IsNotNull(dispatcher[location]);

            _log.DebugFormat("fourth dispatch (fail={0})", fail);
            dispatcher.Dispatch(ev);
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            Thread.Sleep(1000); // failure gets dealt with async
            Assert.IsNull(dispatcher[location]);
            MockPlug.Deregister(testUri);
        }
Exemple #35
0
        public void Parallel_chaining_subscription_and_message_propagation()
        {
            var rootPubsub = Plug.New(_hostInfo.Host.LocalMachineUri.At("host", "$pubsub", "subscribers"));
            var goTrigger = new ManualResetEvent(false);
            var pubsubResults = new List<Result<Plug>>();
            for(var i = 0; i < 10; i++) {
                pubsubResults.Add(Async.ForkThread(() => {
                    goTrigger.WaitOne();
                    return CreatePubSubService("upstream", new XDoc("config").Start("downstream").Elem("uri", rootPubsub).End()).WithInternalKey().AtLocalHost;
                }, new Result<Plug>()));
            }
            var subscriberResults = new List<Result<Tuplet<XUri, AutoMockPlug>>>();
            for(var i = 0; i < 20; i++) {
                var mockUri = new XUri("http://mock/" + i);
                subscriberResults.Add(Async.ForkThread(() => {
                    goTrigger.WaitOne();
                    rootPubsub.With("apikey", _hostInfo.ApiKey).Post(new XDoc("subscription-set")
                        .Elem("uri.owner", mockUri)
                        .Start("subscription")
                            .Attr("id", "1")
                            .Elem("channel", "channel://foo/bar")
                            .Start("recipient").Elem("uri", mockUri).End()
                        .End());
                    var mock = MockPlug.Register(mockUri);
                    return new Tuplet<XUri, AutoMockPlug>(mockUri, mock);
                }, new Result<Tuplet<XUri, AutoMockPlug>>()));
            }
            goTrigger.Set();
            var pubsubs = new List<Plug>();
            foreach(var r in pubsubResults) {
                pubsubs.Add(r.Wait());
            }
            var endpoints = new List<XUri>();
            var mocks = new List<AutoMockPlug>();
            foreach(var r in subscriberResults) {
                var v = r.Wait();
                endpoints.Add(v.Item1);
                mocks.Add(v.Item2);
            }
            foreach(var pubsub in pubsubs) {
                Plug plug = pubsub;
                Wait.For(() => {
                    var set = plug.At("subscribers").Get();
                    return set.ToDocument()["subscription/recipient"].ListLength == endpoints.Count;
                }, TimeSpan.FromSeconds(10));
            }
            var ev = new DispatcherEvent(new XDoc("blah"), new XUri("channel://foo/bar"), new XUri("http://foobar.com/some/page"));

            foreach(var mock in mocks) {
                mock.Expect().Verb("POST").RequestDocument(ev.AsDocument()).Response(DreamMessage.Ok());
            }
            pubsubs[0].At("publish").Post(ev.AsMessage());
            foreach(var mock in mocks) {
                Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure);
            }
        }
Exemple #36
0
        /// <summary>
        /// Dispatch an event against the registered subscriptions.
        /// </summary>
        /// <param name="ev">Dispatch event instance.</param>
        public void Dispatch(DispatcherEvent ev)
        {
            if(ev.HasVisited(_owner)) {

                // this event is in a dispatch loop, so we drop it
                if(_log.IsWarnEnabled) {
                    _log.WarnFormat("event for channel '{0}' already visited the service, dropping", ev.Channel);
                    if(_log.IsDebugEnabled) {
                        _log.Debug("  event origin:");
                        foreach(XUri origin in ev.Origins) {
                            _log.DebugFormat("    - {0}", origin);
                        }
                        _log.Debug("  event route:");
                        foreach(XUri via in ev.Via) {
                            _log.DebugFormat("    - {0}", via);
                        }
                    }
                }
                throw new DreamBadRequestException("Dispatch loop detected: The event has already been dispatched by this service");
            }
            if(_log.IsDebugEnabled) {
                _log.DebugFormat("Dispatcher '{0}' dispatching '{1}' on channel '{2}' with resource '{3}'",
                                        _owner,
                                        ev.Id,
                                        ev.Channel,
                                        ev.Resource);
            }
            DispatcherEvent dispatchEvent = ev.WithVia(_owner);
            if(!_dispatchQueue.TryEnqueue(dispatchEvent)) {
                throw new InvalidOperationException(string.Format("Enqueue of '{0}' failed.", dispatchEvent.Id));
            }
        }
Exemple #37
0
        public void Can_aggregate_from_multiple_dream_hosts()
        {
            // set up hosts
            _log.DebugFormat("---- creating upstream hosts");
            var sourceHost1 = DreamTestHelper.CreateRandomPortHost();
            var source1PubSub = Plug.New(sourceHost1.LocalHost.At("host", "$pubsub").With("apikey", sourceHost1.ApiKey));
            var sourceHost2 = DreamTestHelper.CreateRandomPortHost();
            var source2PubSub = Plug.New(sourceHost2.LocalHost.At("host", "$pubsub").With("apikey", sourceHost2.ApiKey));

            // create aggregator
            _log.DebugFormat("---- creating downstream host");
            var aggregatorPath = "pubsubaggregator";
            var aggregatorHost = DreamTestHelper.CreateRandomPortHost();
            aggregatorHost.Host.RunScripts(new XDoc("config")
                .Start("script").Start("action")
                    .Attr("verb", "POST")
                    .Attr("path", "/host/services")
                    .Start("config")
                        .Elem("path", aggregatorPath)
                        .Elem("sid", "sid://mindtouch.com/dream/2008/10/pubsub")
                        .Elem("apikey", "abc")
                        .Start("upstream")
                            .Elem("uri", source1PubSub.At("subscribers"))
                            .Elem("uri", source2PubSub.At("subscribers"))
                    .End()
                .End().End(), null);
            var aggregatorPubSub = aggregatorHost.LocalHost.At(aggregatorPath).With("apikey", "abc");

            // create subscription
            _log.DebugFormat("---- create downstream subscription");
            var testUri = new XUri("http://mock/aggregator");
            var serviceKey = "1234";
            var accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri);
            var subscriberApiKey = "xyz";
            var set = new XDoc("subscription-set")
                .Elem("uri.owner", "http:///owner1")
                .Start("subscription")
                    .Attr("id", "1")
                    .Add(accessCookie.AsSetCookieDocument)
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient")
                        .Attr("authtoken", subscriberApiKey)
                        .Elem("uri", testUri)
                    .End()
                .End();
            var r = aggregatorPubSub.At("subscribers").PostAsync(set).Wait();
            Assert.IsTrue(r.IsSuccessful, r.Status.ToString());
            Assert.AreEqual(DreamStatus.Created, r.Status);

            // Verify that upstream host pubsub services have the subscription
            Func<DreamMessage, bool> waitFunc = response => {
                var sub = response.ToDocument()["subscription-set/subscription[channel='channel:///foo/*']"];
                return (!sub.IsEmpty
                        && sub["recipient/uri"].AsText.EqualsInvariantIgnoreCase(testUri.ToString())
                        && sub["recipient/@authtoken"].AsText.EqualsInvariant(subscriberApiKey));
            };
            Assert.IsTrue(WaitFor(source1PubSub.At("diagnostics", "subscriptions"), waitFunc, TimeSpan.FromSeconds(5)), "source 1 didn't get the subscription");
            Assert.IsTrue(WaitFor(source2PubSub.At("diagnostics", "subscriptions"), waitFunc, TimeSpan.FromSeconds(5)), "source 2 didn't get the subscription");

            // set up destination mock
            DispatcherEvent aggregatorEvent = new DispatcherEvent(
                new XDoc("aggregator"),
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            DispatcherEvent source1Event = new DispatcherEvent(
                new XDoc("source1"),
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            DispatcherEvent source2Event = new DispatcherEvent(
                new XDoc("source2"),
                new XUri("channel:///foo/bar"),
                new XUri("http://foobar.com/some/page"));
            var mock = MockPlug.Register(testUri);

            // Publish event into aggregator
            mock.Expect().Verb("POST").RequestDocument(aggregatorEvent.AsDocument());
            r = aggregatorPubSub.At("publish").PostAsync(aggregatorEvent.AsMessage()).Wait();
            Assert.IsTrue(r.IsSuccessful, r.Status.ToString());
            Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure);

            // Publish event into source1
            mock.Reset();
            mock.Expect().Verb("POST").RequestDocument(source1Event.AsDocument());
            r = source1PubSub.At("publish").PostAsync(source1Event.AsMessage()).Wait();
            Assert.IsTrue(r.IsSuccessful, r.Status.ToString());
            Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure);

            // Publish event into source2
            mock.Reset();
            mock.Expect().Verb("POST").RequestDocument(source2Event.AsDocument());
            r = source2PubSub.At("publish").PostAsync(source2Event.AsMessage()).Wait();
            Assert.IsTrue(r.IsSuccessful, r.Status.ToString());
            Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure);
        }
Exemple #38
0
        public void Dispatch_will_send_https_resources_to_subscriptions_without_resource()
        {
            DispatcherEvent ev = new DispatcherEvent(
                new XDoc("msg"),
                new XUri("channel:///foo/bar"),
                new XUri("https://foobar.com/some/page"));
            XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4));
            AutoResetEvent resetEvent = new AutoResetEvent(false);
            MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) {
                resetEvent.Set();
                response.Return(DreamMessage.Ok());
            });
            Plug owner = Plug.New("mock:///pubsub");
            DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/"));
            Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie });
            AutoResetEvent setResetEvent = new AutoResetEvent(false);
            dispatcher.CombinedSetUpdated += delegate {
                setResetEvent.Set();
            };
            dispatcher.RegisterSet(
                new XDoc("subscription-set")
                    .Elem("uri.owner", "http:///owner1")
                    .Start("subscription")
                        .Attr("id", "1")
                        .Elem("channel", "channel:///foo/*")
                        .Start("recipient").Elem("uri", testUri).End()
                    .End());

            // combinedset updates happen asynchronously, so give'em a chance
            Assert.IsTrue(setResetEvent.WaitOne(10000, false));
            dispatcher.Dispatch(ev);

            // dispatch happens async on a worker thread
            Assert.IsTrue(resetEvent.WaitOne(10000, false));
            MockPlug.Deregister(testUri);
        }