add new AddServiceEndpoint() overload in 4.0.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel / ServiceHostBase.cs
index bba866c506e481e343fa08a6a4d13b6521e42009..52bb1ed723e0d72aed7641f7267d62829f39bb0e 100644 (file)
@@ -35,17 +35,20 @@ using System.ServiceModel.Description;
 using System.ServiceModel.Dispatcher;
 using System.ServiceModel.Security;
 using System.Reflection;
+using System.Threading;
 
 namespace System.ServiceModel
 {
        public abstract partial class ServiceHostBase
                : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
        {
+               // It is used for mapping a ServiceHostBase to HttpChannelListener precisely.
+               internal static ServiceHostBase CurrentServiceHostHack;
+
                ServiceCredentials credentials;
                ServiceDescription description;
                UriSchemeKeyedCollection base_addresses;
                TimeSpan open_timeout, close_timeout, instance_idle_timeout;
-               ServiceThrottle throttle;
                List<InstanceContext> contexts;
                ReadOnlyCollection<InstanceContext> exposed_contexts;
                ChannelDispatcherCollection channel_dispatchers;
@@ -59,7 +62,6 @@ namespace System.ServiceModel
                        close_timeout = DefaultCloseTimeout;
 
                        credentials = new ServiceCredentials ();
-                       throttle = new ServiceThrottle ();
                        contexts = new List<InstanceContext> ();
                        exposed_contexts = new ReadOnlyCollection<InstanceContext> (contexts);
                        channel_dispatchers = new ChannelDispatcherCollection (this);
@@ -116,7 +118,6 @@ namespace System.ServiceModel
                        private set;
                }
 
-               [MonoTODO]
                public ServiceCredentials Credentials {
                        get { return credentials; }
                }
@@ -129,7 +130,6 @@ namespace System.ServiceModel
                        get { return contracts; }
                }
 
-               [MonoTODO]
                public IExtensionCollection<ServiceHostBase> Extensions {
                        get {
                                if (extensions == null)
@@ -189,20 +189,39 @@ namespace System.ServiceModel
                        string implementedContract, Binding binding,
                        Uri address)
                {
-                       return AddServiceEndpoint (implementedContract, binding, address, address);
+                       return AddServiceEndpoint (implementedContract, binding, address, null);
                }
 
                public ServiceEndpoint AddServiceEndpoint (
                        string implementedContract, Binding binding,
                        Uri address, Uri listenUri)
                {
-                       EndpointAddress ea = BuildEndpointAddress (address, binding);
+                       EndpointAddress ea = new EndpointAddress (BuildAbsoluteUri (address, binding));
                        ContractDescription cd = GetContract (implementedContract, binding.Namespace == "http://schemas.microsoft.com/ws/2005/02/mex/bindings");
                        if (cd == null)
                                throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
                        return AddServiceEndpointCore (cd, binding, ea, listenUri);
                }
 
+#if NET_4_0
+               public virtual void AddServiceEndpoint (ServiceEndpoint endpoint)
+               {
+                       if (endpoint == null)
+                               throw new ArgumentNullException ("endpoint");
+
+                       ThrowIfDisposedOrImmutable ();
+
+                       if (endpoint.Address == null)
+                               throw new ArgumentException ("Address on the argument endpoint is null");
+                       if (endpoint.Contract == null)
+                               throw new ArgumentException ("Contract on the argument endpoint is null");
+                       if (endpoint.Binding == null)
+                               throw new ArgumentException ("Binding on the argument endpoint is null");
+
+                       Description.Endpoints.Add (endpoint);
+               }
+#endif
+
                Type PopulateType (string typeName)
                {
                        Type type = Type.GetType (typeName);
@@ -258,7 +277,7 @@ namespace System.ServiceModel
                        return null;
                }
 
-               internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
+               internal Uri BuildAbsoluteUri (Uri address, Binding binding)
                {
                        if (!address.IsAbsoluteUri) {
                                // Find a Base address with matching scheme,
@@ -272,17 +291,21 @@ namespace System.ServiceModel
                                        baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
                                address = new Uri (baseaddr, address);
                        }
-                       return new EndpointAddress (address);
+                       return address;
                }
 
                internal ServiceEndpoint AddServiceEndpointCore (
                        ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
                {
+                       if (listenUri != null)
+                               listenUri = BuildAbsoluteUri (listenUri, binding);
+
                        foreach (ServiceEndpoint e in Description.Endpoints)
                                if (e.Contract == cd && e.Binding == binding && e.Address == address && e.ListenUri.Equals (listenUri))
                                        return e;
                        ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
-                       se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
+                       // FIXME: should we reject relative ListenUri?
+                       se.ListenUri = listenUri ?? address.Uri;
                        Description.Endpoints.Add (se);
                        return se;
                }
@@ -293,8 +316,6 @@ namespace System.ServiceModel
                                throw new InvalidOperationException ("ApplyConfiguration requires that the Description property be initialized. Either provide a valid ServiceDescription in the CreateDescription method or override the ApplyConfiguration method to provide an alternative implementation");
 
                        ServiceElement service = GetServiceElement ();
-
-                       //TODO: Should we call here LoadServiceElement ?
                        if (service != null) {
                                
                                //base addresses
@@ -304,7 +325,6 @@ namespace System.ServiceModel
                                }
 
                                // behaviors
-                               // TODO: use EvaluationContext of ServiceElement.
                                ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors [service.BehaviorConfiguration];
                                if (behavior != null) {
                                        foreach (var bxe in behavior) {
@@ -312,13 +332,15 @@ namespace System.ServiceModel
                                                Description.Behaviors.Add (b);
                                        }
                                }
+                               else
+                                       throw new ArgumentException (String.Format ("Service behavior {0} is specified, but was not found", service.BehaviorConfiguration));
 
                                // services
                                foreach (ServiceEndpointElement endpoint in service.Endpoints) {
                                        ServiceEndpoint se = AddServiceEndpoint (
                                                endpoint.Contract,
                                                ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
-                                               endpoint.Address.ToString ());
+                                               endpoint.Address);
                                        // endpoint behaviors
                                        EndpointBehaviorElement epbehavior = ConfigUtil.BehaviorsSection.EndpointBehaviors [endpoint.BehaviorConfiguration];
                                        if (epbehavior != null)
@@ -375,28 +397,36 @@ namespace System.ServiceModel
                        //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
                        //We must keep thet ServiceEndpoints as a seperate collection, since the user
                        //can change the collection in the description during the behaviors events.
-                       Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
                        ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
                        Description.Endpoints.CopyTo (endPoints, 0);
+                       var builder = new DispatcherBuilder (this);
                        foreach (ServiceEndpoint se in endPoints) {
-
                                var commonParams = new BindingParameterCollection ();
                                foreach (IServiceBehavior b in Description.Behaviors)
                                        b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
 
-                               var channel = new DispatcherBuilder ().BuildChannelDispatcher (Description.ServiceType, se, commonParams);
-                               ChannelDispatchers.Add (channel);
-                               endPointToDispatcher[se] = channel;
+                               var channel = builder.BuildChannelDispatcher (Description.ServiceType, se, commonParams);
+                               if (!ChannelDispatchers.Contains (channel))
+                                       ChannelDispatchers.Add (channel);
                        }
 
                        //After the ChannelDispatchers are created, and attached to the service host
                        //Apply dispatching behaviors.
+                       //
+                       // This behavior application order is tricky: first only
+                       // ServiceDebugBehavior and ServiceMetadataBehavior are
+                       // applied, and then other service behaviors are applied.
+                       // It is because those two behaviors adds ChannelDispatchers
+                       // and any other service behaviors must be applied to
+                       // those newly populated dispatchers.
+                       foreach (IServiceBehavior b in Description.Behaviors)
+                               if (b is ServiceMetadataBehavior || b is ServiceDebugBehavior)
+                                       b.ApplyDispatchBehavior (Description, this);
                        foreach (IServiceBehavior b in Description.Behaviors)
-                               b.ApplyDispatchBehavior (Description, this);
+                               if (!(b is ServiceMetadataBehavior || b is ServiceDebugBehavior))
+                                       b.ApplyDispatchBehavior (Description, this);
 
-                       foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
-                               foreach (var ed in val.Value.Endpoints)
-                                       ApplyDispatchBehavior (ed, val.Key);                    
+                       builder.ApplyDispatchBehaviors ();
                }
 
                private void ValidateDescription ()
@@ -410,19 +440,6 @@ namespace System.ServiceModel
                                throw new InvalidOperationException ("The ServiceHost must have at least one application endpoint (that does not include metadata exchange contract) defined by either configuration, behaviors or call to AddServiceEndpoint methods.");
                }
 
-               private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
-               {
-                       foreach (IContractBehavior b in endPoint.Contract.Behaviors)
-                               b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
-                       foreach (IEndpointBehavior b in endPoint.Behaviors)
-                               b.ApplyDispatchBehavior (endPoint, ed);
-                       foreach (OperationDescription operation in endPoint.Contract.Operations) {
-                               foreach (IOperationBehavior b in operation.Behaviors)
-                                       b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
-                       }
-
-               }
-
                [MonoTODO]
                protected void LoadConfigurationSection (ServiceElement element)
                {
@@ -476,11 +493,27 @@ namespace System.ServiceModel
                {
                        DateTime start = DateTime.Now;
                        InitializeRuntime ();
-                       foreach (var cd in ChannelDispatchers)
+                       for (int i = 0; i < ChannelDispatchers.Count; i++) {
+                               // Skip ServiceMetadataExtension-based one. special case.
+                               for (int j = i + 1; j < ChannelDispatchers.Count; j++) {
+                                       var cd1 = ChannelDispatchers [i];
+                                       var cd2 = ChannelDispatchers [j];
+                                       if (cd1.IsMex || cd2.IsMex)
+                                               continue;
+                                       if (cd1.Listener.Uri.Equals (cd2.Listener.Uri))
+                                               throw new InvalidOperationException ("Two or more service endpoints with different Binding instance are bound to the same listen URI.");
+                               }
+                       }
+
+                       var waits = new List<ManualResetEvent> ();
+                       foreach (var cd in ChannelDispatchers) {
+                               var wait = new ManualResetEvent (false);
+                               cd.Opened += delegate { wait.Set (); };
+                               waits.Add (wait);
                                cd.Open (timeout - (DateTime.Now - start));
+                       }
 
-                       // FIXME: remove this hack. It should make sure that each ChannelDispatcher's loop has started, using WaitHandle.WaitAll() or something similar.
-                       System.Threading.Thread.Sleep (300);
+                       WaitHandle.WaitAll (waits.ToArray ());
                }
 
                protected override void OnEndClose (IAsyncResult result)
@@ -511,104 +544,82 @@ namespace System.ServiceModel
                {
                        Close ();
                }
+       }
 
-               /*
-               class SyncMethodInvoker : IOperationInvoker
-               {
-                       readonly MethodInfo _methodInfo;
-                       public SyncMethodInvoker (MethodInfo methodInfo) {
-                               _methodInfo = methodInfo;
-                       }
-                       
-                       #region IOperationInvoker Members
-
-                       public bool IsSynchronous {
-                               get { return true; }
-                       }
-
-                       public object [] AllocateParameters () {
-                               return new object [_methodInfo.GetParameters ().Length];
-                       }
-
-                       public object Invoke (object instance, object [] parameters)
-            {
-                               return _methodInfo.Invoke (instance, parameters);
-                       }
-
-                       public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
-                               throw new NotSupportedException ();
-                       }
-
-                       public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
-                               throw new NotSupportedException ();
-                       }
-
-                       #endregion
-               }
+       /// <summary>
+       ///  Builds ChannelDispatchers as appropriate to service the service endpoints. 
+       /// </summary>
+       partial class DispatcherBuilder
+       {
+               ServiceHostBase host;
 
-               class AsyncMethodInvoker : IOperationInvoker
+               public DispatcherBuilder (ServiceHostBase host)
                {
-                       readonly MethodInfo _beginMethodInfo, _endMethodInfo;
-                       public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
-                               _beginMethodInfo = beginMethodInfo;
-                               _endMethodInfo = endMethodInfo;
-                       }
-
-                       #region IOperationInvoker Members
-
-                       public bool IsSynchronous {
-                               get { return false; }
-                       }
-
-                       public object [] AllocateParameters () {
-                               return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
-                       }
-
-                       public object Invoke (object instance, object [] parameters) {
-                               throw new NotImplementedException ("Can't invoke async method synchronously");
-                               //BUGBUG: need to differentiate between input and output parameters.
-                               IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
-                               asyncResult.AsyncWaitHandle.WaitOne();
-                               return InvokeEnd(instance, out parameters, asyncResult);
-                       }
-
-                       public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
-                               if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
-                                       throw new ArgumentException ("Wrong number of input parameters");
-                               object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
-                               Array.Copy (inputs, fullargs, inputs.Length);
-                               fullargs [inputs.Length] = callback;
-                               fullargs [inputs.Length + 1] = state;
-                               return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
-                       }
+                       this.host = host;
+               }
 
-                       public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
-                               outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
-                               object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
-                               fullargs [outputs.Length] = asyncResult;
-                               object result = _endMethodInfo.Invoke (instance, fullargs);
-                               Array.Copy (fullargs, outputs, outputs.Length);
-                               return result;
-                       }
+               Dictionary<Binding,ChannelDispatcher> built_dispatchers = new Dictionary<Binding,ChannelDispatcher> ();
+               Dictionary<ServiceEndpoint, EndpointDispatcher> ep_to_dispatcher_ep = new Dictionary<ServiceEndpoint, EndpointDispatcher> ();
 
-                       #endregion
-               }
-               */
-       }
+               internal static Action<ChannelDispatcher> ChannelDispatcherSetter;
 
-       partial class DispatcherBuilder
-       {
                internal ChannelDispatcher BuildChannelDispatcher (Type serviceType, ServiceEndpoint se, BindingParameterCollection commonParams)
                {
                        //Let all behaviors add their binding parameters
                        AddBindingParameters (commonParams, se);
-                       //User the binding parameters to build the channel listener and Dispatcher
-                       IChannelListener lf = BuildListener (se, commonParams);
-                       ChannelDispatcher cd = new ChannelDispatcher (
-                               lf, se.Binding.Name);
-                       cd.InitializeServiceEndpoint (serviceType, se);
+                       
+                       // See if there's an existing channel that matches this endpoint
+                       var version = se.Binding.GetProperty<MessageVersion> (commonParams);
+                       if (version == null)
+                               throw new InvalidOperationException ("At least one BindingElement in the Binding must override GetProperty method to return a MessageVersion and no prior binding element should return null instead of calling GetInnerProperty method on BindingContext.");
+
+                       ChannelDispatcher cd = FindExistingDispatcher (se);
+                       EndpointDispatcher ep;
+                       if (cd != null) {
+                               ep = cd.InitializeServiceEndpoint (serviceType, se);
+                       } else {
+                               // Use the binding parameters to build the channel listener and Dispatcher.
+                               lock (HttpTransportBindingElement.ListenerBuildLock) {
+                                       ServiceHostBase.CurrentServiceHostHack = host;
+                                       IChannelListener lf = BuildListener (se, commonParams);
+                                       cd = new ChannelDispatcher (lf, se.Binding.Name);
+                                       cd.MessageVersion = version;
+                                       if (ChannelDispatcherSetter != null) {
+                                               ChannelDispatcherSetter (cd);
+                                               ChannelDispatcherSetter = null;
+                                       }
+                                       ServiceHostBase.CurrentServiceHostHack = null;
+                               }
+                               ep = cd.InitializeServiceEndpoint (serviceType, se);
+                               built_dispatchers.Add (se.Binding, cd);
+                       }
+                       ep_to_dispatcher_ep[se] = ep;
                        return cd;
                }
+               
+               ChannelDispatcher FindExistingDispatcher (ServiceEndpoint se)
+               {
+                       return built_dispatchers.FirstOrDefault ((KeyValuePair<Binding,ChannelDispatcher> p) => se.Binding == p.Key).Value;
+               }
+
+               internal void ApplyDispatchBehaviors ()
+               {
+                       foreach (KeyValuePair<ServiceEndpoint, EndpointDispatcher> val in ep_to_dispatcher_ep)
+                               ApplyDispatchBehavior (val.Value, val.Key);
+               }
+               
+               private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
+               {
+                       foreach (IContractBehavior b in endPoint.Contract.Behaviors)
+                               b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
+                       foreach (IEndpointBehavior b in endPoint.Behaviors)
+                               b.ApplyDispatchBehavior (endPoint, ed);
+                       foreach (OperationDescription operation in endPoint.Contract.Operations) {
+                               foreach (IOperationBehavior b in operation.Behaviors)
+                                       b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
+                       }
+
+               }
 
                private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {