Add (not-working) callback instance context mode test and FIXME comment.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel / ClientRuntimeChannel.cs
index 68f664e212d5a6078473904e0c1bb2bd555a4a2e..97d603be14e3e2681f6563ca25835c6f2c9367a3 100644 (file)
@@ -26,7 +26,9 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 using System;
+using System.Collections.Generic;
 using System.Reflection;
+using System.Runtime.Serialization;
 using System.ServiceModel.Channels;
 using System.ServiceModel.Description;
 using System.ServiceModel.Dispatcher;
@@ -34,61 +36,38 @@ using System.ServiceModel.Security;
 using System.Threading;
 using System.Xml;
 
-namespace System.ServiceModel
+namespace System.ServiceModel.MonoInternal
 {
-#if NET_2_1
-       internal class DuplexClientRuntimeChannel
-       {
-       }
-#else
-       internal class DuplexClientRuntimeChannel
-               : ClientRuntimeChannel, IDuplexContextChannel
+#if DISABLE_REAL_PROXY
+       // FIXME: This is a quick workaround for bug #571907
+       public
+#endif
+       interface IInternalContextChannel
        {
-               public DuplexClientRuntimeChannel (ServiceEndpoint endpoint,
-                       ChannelFactory factory, EndpointAddress remoteAddress, Uri via)
-                       : base (endpoint, factory, remoteAddress, via)
-               {
-               }
+               ContractDescription Contract { get; }
 
-               public bool AutomaticInputSessionShutdown {
-                       get { throw new NotImplementedException (); }
-                       set { throw new NotImplementedException (); }
-               }
-
-               public InstanceContext CallbackInstance { get; set; }
-
-               Action<TimeSpan> session_shutdown_delegate;
+               object Process (MethodBase method, string operationName, object [] parameters);
 
-               public void CloseOutputSession (TimeSpan timeout)
-               {
-                       throw new NotImplementedException ();
-               }
+               IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState);
 
-               public IAsyncResult BeginCloseOutputSession (TimeSpan timeout, AsyncCallback callback, object state)
-               {
-                       if (session_shutdown_delegate == null)
-                               session_shutdown_delegate = new Action<TimeSpan> (CloseOutputSession);
-                       return session_shutdown_delegate.BeginInvoke (timeout, callback, state);
-               }
-
-               public void EndCloseOutputSession (IAsyncResult result)
-               {
-                       session_shutdown_delegate.EndInvoke (result);
-               }
+               object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result);
        }
-#endif
 
-       internal class ClientRuntimeChannel
-               : CommunicationObject, IClientChannel
+#if DISABLE_REAL_PROXY
+       // FIXME: This is a quick workaround for bug #571907
+       public
+#endif
+       class ClientRuntimeChannel
+               : CommunicationObject, IClientChannel, IInternalContextChannel
        {
                ClientRuntime runtime;
                EndpointAddress remote_address;
                ContractDescription contract;
                MessageVersion message_version;
-               IChannelFactory factory;
                TimeSpan default_open_timeout, default_close_timeout;
-               IRequestChannel request_channel;
-               IOutputChannel output_channel; // could also be IDuplexChannel instance.
+               IChannel channel;
+               IChannelFactory factory;
+               OperationContext context;
 
                #region delegates
                readonly ProcessDelegate _processDelegate;
@@ -106,15 +85,22 @@ namespace System.ServiceModel
 
                public ClientRuntimeChannel (ServiceEndpoint endpoint,
                        ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
+                       : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
                {
-                       this.runtime = endpoint.CreateRuntime ();
-                       this.remote_address = remoteAddress ?? endpoint.Address;
-                       runtime.Via = via;
-                       this.contract = endpoint.Contract;
-                       this.message_version = endpoint.Binding.MessageVersion;
-                       this.factory = channelFactory.OpenedChannelFactory;
-                       default_open_timeout = channelFactory.DefaultOpenTimeout;
-                       default_close_timeout = channelFactory.DefaultCloseTimeout;
+               }
+
+               public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
+               {
+                       if (runtime == null)
+                               throw new ArgumentNullException ("runtime");
+                       this.runtime = runtime;
+                       this.remote_address = remoteAddress;
+                       if (runtime.Via == null)
+                               runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
+                       this.contract = contract;
+                       this.message_version = messageVersion;
+                       default_open_timeout = openTimeout;
+                       default_close_timeout = closeTimeout;
                        _processDelegate = new ProcessDelegate (Process);
                        requestDelegate = new RequestDelegate (Request);
                        sendDelegate = new SendDelegate (Send);
@@ -123,18 +109,42 @@ namespace System.ServiceModel
                        AllowInitializationUI = true;
                        OperationTimeout = TimeSpan.FromMinutes (1);
 
-                       // determine operation channel to create.
-                       if (factory is IChannelFactory<IRequestChannel> ||
-                           factory is IChannelFactory<IRequestSessionChannel>)
-                               SetupRequestChannel ();
-                       else
-                               SetupOutputChannel ();
+                       if (contextChannel != null)
+                               channel = contextChannel;
+                       else {
+                               var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
+                               try {
+                                       channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
+                                       this.factory = factory;
+                               } catch (TargetInvocationException ex) {
+                                       if (ex.InnerException != null)
+                                               throw ex.InnerException;
+                                       else
+                                               throw;
+                               }
+                       }
+               }
+
+               public ContractDescription Contract {
+                       get { return contract; }
                }
 
                public ClientRuntime Runtime {
                        get { return runtime; }
                }
 
+               IRequestChannel RequestChannel {
+                       get { return channel as IRequestChannel; }
+               }
+
+               IOutputChannel OutputChannel {
+                       get { return channel as IOutputChannel; }
+               }
+
+               internal IDuplexChannel DuplexChannel {
+                       get { return channel as IDuplexChannel; }
+               }
+
                #region IClientChannel
 
                bool did_interactive_initialization;
@@ -174,7 +184,7 @@ namespace System.ServiceModel
 
                        public override bool WaitOne (int millisecondsTimeout)
                        {
-                               return WaitOne (millisecondsTimeout, false);
+                               return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
                        }
 
                        WaitHandle [] ResultWaitHandles {
@@ -186,6 +196,7 @@ namespace System.ServiceModel
                                }
                        }
 
+#if !MOONLIGHT
                        public override bool WaitOne (int millisecondsTimeout, bool exitContext)
                        {
                                return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
@@ -195,6 +206,7 @@ namespace System.ServiceModel
                        {
                                return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
                        }
+#endif
                }
 
                class DisplayUIAsyncResult : IAsyncResult
@@ -295,11 +307,11 @@ namespace System.ServiceModel
 
                public IInputSession InputSession {
                        get {
-                               ISessionChannel<IInputSession> ch = request_channel as ISessionChannel<IInputSession>;
-                               ch = ch ?? output_channel as ISessionChannel<IInputSession>;
+                               ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
+                               ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
                                if (ch != null)
                                        return ch.Session;
-                               var dch = output_channel as ISessionChannel<IDuplexSession>;
+                               var dch = OutputChannel as ISessionChannel<IDuplexSession>;
                                return dch != null ? dch.Session : null;
                        }
                }
@@ -316,17 +328,17 @@ namespace System.ServiceModel
 
                public IOutputSession OutputSession {
                        get {
-                               ISessionChannel<IOutputSession> ch = request_channel as ISessionChannel<IOutputSession>;
-                               ch = ch ?? output_channel as ISessionChannel<IOutputSession>;
+                               ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
+                               ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
                                if (ch != null)
                                        return ch.Session;
-                               var dch = output_channel as ISessionChannel<IDuplexSession>;
+                               var dch = OutputChannel as ISessionChannel<IDuplexSession>;
                                return dch != null ? dch.Session : null;
                        }
                }
 
                public EndpointAddress RemoteAddress {
-                       get { return request_channel != null ? request_channel.RemoteAddress : output_channel.RemoteAddress; }
+                       get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
                }
 
                public string SessionId {
@@ -346,45 +358,62 @@ namespace System.ServiceModel
 
                protected override void OnAbort ()
                {
-                       factory.Abort ();
+                       channel.Abort ();
+                       if (factory != null) // ... is it valid?
+                               factory.Abort ();
                }
 
+               Action<TimeSpan> close_delegate;
+
                protected override IAsyncResult OnBeginClose (
                        TimeSpan timeout, AsyncCallback callback, object state)
                {
-                       return factory.BeginClose (timeout, callback, state);
+                       if (close_delegate == null)
+                               close_delegate = new Action<TimeSpan> (OnClose);
+                       return close_delegate.BeginInvoke (timeout, callback, state);
                }
 
                protected override void OnEndClose (IAsyncResult result)
                {
-                       factory.EndClose (result);
+                       close_delegate.EndInvoke (result);
                }
 
                protected override void OnClose (TimeSpan timeout)
                {
-                       factory.Close (timeout);
+                       DateTime start = DateTime.Now;
+                       if (channel.State == CommunicationState.Opened)
+                               channel.Close (timeout);
                }
 
+               Action<TimeSpan> open_callback;
+
                protected override IAsyncResult OnBeginOpen (
                        TimeSpan timeout, AsyncCallback callback, object state)
                {
-                       throw new SystemException ("INTERNAL ERROR: this should not be called (or not supported yet)");
+                       if (open_callback == null)
+                               open_callback = new Action<TimeSpan> (OnOpen);
+                       return open_callback.BeginInvoke (timeout, callback, state);
                }
 
                protected override void OnEndOpen (IAsyncResult result)
                {
+                       if (open_callback == null)
+                               throw new InvalidOperationException ("Async open operation has not started");
+                       open_callback.EndInvoke (result);
                }
 
                protected override void OnOpen (TimeSpan timeout)
                {
                        if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
                                throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
+                       if (channel.State == CommunicationState.Created)
+                               channel.Open (timeout);
                }
 
                // IChannel
 
                IChannel OperationChannel {
-                       get { return (IChannel) request_channel ?? output_channel; }
+                       get { return channel; }
                }
 
                public T GetProperty<T> () where T : class
@@ -393,20 +422,31 @@ namespace System.ServiceModel
                }
 
                // IExtensibleObject<IContextChannel>
-               [MonoTODO]
+
+               IExtensionCollection<IContextChannel> extensions;
+
                public IExtensionCollection<IContextChannel> Extensions {
-                       get { throw new NotImplementedException (); }
+                       get {
+                               if (extensions == null)
+                                       extensions = new ExtensionCollection<IContextChannel> (this);
+                               return extensions;
+                       }
                }
 
                #region Request/Output processing
 
                public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
                {
+                       if (context != null)
+                               throw new InvalidOperationException ("another operation is in progress");
+                       context = OperationContext.Current;
+
                        return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
                }
 
                public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
                {
+                       context = null;
                        if (result == null)
                                throw new ArgumentNullException ("result");
                        if (parameters == null)
@@ -421,8 +461,10 @@ namespace System.ServiceModel
                        try {
                                return DoProcess (method, operationName, parameters);
                        } catch (Exception ex) {
+#if MOONLIGHT // just for debugging
                                Console.Write ("Exception in async operation: ");
                                Console.WriteLine (ex);
+#endif
                                throw;
                        }
                }
@@ -432,6 +474,10 @@ namespace System.ServiceModel
                        if (AllowInitializationUI)
                                DisplayInitializationUI ();
                        OperationDescription od = SelectOperation (method, operationName, parameters);
+
+                       if (State != CommunicationState.Opened)
+                               Open ();
+
                        if (!od.IsOneWay)
                                return Request (od, parameters);
                        else {
@@ -453,40 +499,14 @@ namespace System.ServiceModel
                        return od;
                }
 
-               // This handles IDuplexChannel, IOutputChannel, and those for session channels.
-               void SetupOutputChannel ()
-               {
-                       if (output_channel != null)
-                               return;
-
-                       var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
-                       output_channel = (IOutputChannel) method.Invoke (factory, new object [] {remote_address, Via});
-               }
-
-               // This handles both IRequestChannel and IRequestSessionChannel.
-               void SetupRequestChannel ()
-               {
-                       if (request_channel != null)
-                               return;
-
-                       var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
-                       request_channel = (IRequestChannel) method.Invoke (factory, new object [] {remote_address, Via});
-               }
-
                void Output (OperationDescription od, object [] parameters)
                {
-                       if (output_channel.State != CommunicationState.Opened)
-                               output_channel.Open ();
-
                        ClientOperation op = runtime.Operations [od.Name];
                        Send (CreateRequest (op, parameters), OperationTimeout);
                }
 
                object Request (OperationDescription od, object [] parameters)
                {
-                       if (OperationChannel.State != CommunicationState.Opened)
-                               OperationChannel.Open ();
-
                        ClientOperation op = runtime.Operations [od.Name];
                        object [] inspections = new object [runtime.MessageInspectors.Count];
                        Message req = CreateRequest (op, parameters);
@@ -496,27 +516,40 @@ namespace System.ServiceModel
 
                        Message res = Request (req, OperationTimeout);
                        if (res.IsFault) {
-                               MessageFault fault = MessageFault.CreateFault (res, runtime.MaxFaultSize);
-                               if (fault.HasDetail && fault is MessageFault.SimpleMessageFault) {
-                                       MessageFault.SimpleMessageFault simpleFault = fault as MessageFault.SimpleMessageFault;
-                                       object detail = simpleFault.Detail;
-                                       Type t = detail.GetType ();
-                                       Type faultType = typeof (FaultException<>).MakeGenericType (t);
-                                       object [] constructorParams = new object [] { detail, fault.Reason, fault.Code, fault.Actor };
-                                       FaultException fe = (FaultException) Activator.CreateInstance (faultType, constructorParams);
-                                       throw fe;
-                               }
-                               else {
-                                       // given a MessageFault, it is hard to figure out the type of the embedded detail
-                                       throw new FaultException(fault);
+                               var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
+                               MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
+                               var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
+                               Exception ex;
+                               if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
+                                       if (fault.HasDetail) {
+                                               Type detailType = typeof (ExceptionDetail);
+                                               var freader = fault.GetReaderAtDetailContents ();
+                                               DataContractSerializer ds = null;
+#if !NET_2_1
+                                               foreach (var fci in op.FaultContractInfos)
+                                                       if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
+                                                               detailType = fci.Detail;
+                                                               ds = fci.Serializer;
+                                                               break;
+                                                       }
+#endif
+                                               if (ds == null)
+                                                       ds = new DataContractSerializer (detailType);
+                                               var detail = ds.ReadObject (freader);
+                                               ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
+                                       }
+
+                                       if (ex == null)
+                                               ex = new FaultException (fault);
                                }
+                               throw ex;
                        }
 
                        for (int i = 0; i < inspections.Length; i++)
                                runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
 
                        if (op.DeserializeReply)
-                               return op.GetFormatter ().DeserializeReply (res, parameters);
+                               return op.Formatter.DeserializeReply (res, parameters);
                        else
                                return res;
                }
@@ -525,13 +558,20 @@ namespace System.ServiceModel
                // They are internal for ClientBase<T>.ChannelBase use.
                internal Message Request (Message msg, TimeSpan timeout)
                {
-                       if (request_channel != null)
-                               return request_channel.Request (msg, timeout);
-                       else {
-                               DateTime startTime = DateTime.Now;
-                               output_channel.Send (msg, timeout);
-                               return ((IDuplexChannel) output_channel).Receive (timeout - (DateTime.Now - startTime));
-                       }
+                       if (RequestChannel != null)
+                               return RequestChannel.Request (msg, timeout);
+                       else
+                               return RequestCorrelated (msg, timeout, OutputChannel);
+               }
+
+               internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
+               {
+                       // FIXME: implement ConcurrencyMode check:
+                       // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
+
+                       DateTime startTime = DateTime.Now;
+                       OutputChannel.Send (msg, timeout);
+                       return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
                }
 
                internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
@@ -546,7 +586,10 @@ namespace System.ServiceModel
 
                internal void Send (Message msg, TimeSpan timeout)
                {
-                       output_channel.Send (msg, timeout);
+                       if (OutputChannel != null)
+                               OutputChannel.Send (msg, timeout);
+                       else
+                               RequestChannel.Request (msg, timeout); // and ignore returned message.
                }
 
                internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
@@ -568,15 +611,42 @@ namespace System.ServiceModel
 
                        Message msg;
                        if (op.SerializeRequest)
-                               msg = op.GetFormatter ().SerializeRequest (
+                               msg = op.Formatter.SerializeRequest (
                                        version, parameters);
-                       else
+                       else {
+                               if (parameters.Length != 1)
+                                       throw new ArgumentException (String.Format ("Argument parameters does not match the expected input. It should contain only a Message, but has {0} parameters", parameters.Length));
+                               if (!(parameters [0] is Message))
+                                       throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
                                msg = (Message) parameters [0];
+                       }
 
-                       if (OutputSession != null)
-                               msg.Headers.MessageId = new UniqueId (OutputSession.Id);
+                       context = context ?? OperationContext.Current;
+                       if (context != null) {
+                               // CopyHeadersFrom does not work here (brings duplicates -> error)
+                               foreach (var mh in context.OutgoingMessageHeaders) {
+                                       int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
+                                       if (x >= 0)
+                                               msg.Headers.RemoveAt (x);
+                                       msg.Headers.Add ((MessageHeader) mh);
+                               }
+                               msg.Properties.CopyProperties (context.OutgoingMessageProperties);
+                       }
+
+                       // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
+                       //if (OutputSession != null)
+                       //      msg.Headers.MessageId = new UniqueId (OutputSession.Id);
                        msg.Properties.AllowOutputBatching = AllowOutputBatching;
 
+                       if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
+                               if (msg.Headers.MessageId == null)
+                                       msg.Headers.MessageId = new UniqueId ();
+                               if (msg.Headers.ReplyTo == null)
+                                       msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
+                               if (msg.Headers.To == null && RemoteAddress != null)
+                                       msg.Headers.To = RemoteAddress.Uri;
+                       }
+
                        return msg;
                }