// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; namespace Microsoft.AspNet.SignalR.Hubs { internal class HubPipeline : IHubPipeline, IHubPipelineInvoker { private readonly Stack _modules; private readonly Lazy _pipeline; public HubPipeline() { _modules = new Stack(); _pipeline = new Lazy(() => new ComposedPipeline(_modules)); } public IHubPipeline AddModule(IHubPipelineModule pipelineModule) { if (_pipeline.IsValueCreated) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UnableToAddModulePiplineAlreadyInvoked)); } _modules.Push(pipelineModule); return this; } private ComposedPipeline Pipeline { get { return _pipeline.Value; } } public Task Invoke(IHubIncomingInvokerContext context) { return Pipeline.Invoke(context); } public Task Connect(IHub hub) { return Pipeline.Connect(hub); } public Task Reconnect(IHub hub) { return Pipeline.Reconnect(hub); } public Task Disconnect(IHub hub) { return Pipeline.Disconnect(hub); } public bool AuthorizeConnect(HubDescriptor hubDescriptor, IRequest request) { return Pipeline.AuthorizeConnect(hubDescriptor, request); } public IList RejoiningGroups(HubDescriptor hubDescriptor, IRequest request, IList groups) { return Pipeline.RejoiningGroups(hubDescriptor, request, groups); } public Task Send(IHubOutgoingInvokerContext context) { return Pipeline.Send(context); } private class ComposedPipeline { public Func> Invoke; public Func Connect; public Func Reconnect; public Func Disconnect; public Func AuthorizeConnect; public Func, IList> RejoiningGroups; public Func Send; public ComposedPipeline(Stack modules) { // This wouldn't look nearly as gnarly if C# had better type inference, but now we don't need the ComposedModule or PassThroughModule. Invoke = Compose>>(modules, (m, f) => m.BuildIncoming(f))(HubDispatcher.Incoming); Connect = Compose>(modules, (m, f) => m.BuildConnect(f))(HubDispatcher.Connect); Reconnect = Compose>(modules, (m, f) => m.BuildReconnect(f))(HubDispatcher.Reconnect); Disconnect = Compose>(modules, (m, f) => m.BuildDisconnect(f))(HubDispatcher.Disconnect); AuthorizeConnect = Compose>(modules, (m, f) => m.BuildAuthorizeConnect(f))((h, r) => true); RejoiningGroups = Compose, IList>>(modules, (m, f) => m.BuildRejoiningGroups(f))((h, r, g) => g); Send = Compose>(modules, (m, f) => m.BuildOutgoing(f))(HubDispatcher.Outgoing); } // IHubPipelineModule could be turned into a second generic parameter, but it would make the above invocations even longer than they currently are. private static Func Compose(IEnumerable modules, Func method) { // Notice we are reversing and aggregating in one step. (Function composition is associative) return modules.Aggregate>(x => x, (a, b) => (x => method(b, a(x)))); } } } }