Merge branch 'master' of github.com:mono/mono
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel / ClientRuntimeChannel.cs
1 //
2 // ClientRuntimeChannel.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006 Novell, Inc.  http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Reflection;
30 using System.Runtime.Serialization;
31 using System.ServiceModel.Channels;
32 using System.ServiceModel.Description;
33 using System.ServiceModel.Dispatcher;
34 using System.ServiceModel.Security;
35 using System.Threading;
36 using System.Xml;
37
38 namespace System.ServiceModel.MonoInternal
39 {
40         // FIXME: This is a quick workaround for bug #571907
41         public class ClientRuntimeChannel
42                 : CommunicationObject, IClientChannel
43         {
44                 ClientRuntime runtime;
45                 EndpointAddress remote_address;
46                 ContractDescription contract;
47                 MessageVersion message_version;
48                 TimeSpan default_open_timeout, default_close_timeout;
49                 IChannel channel;
50                 IChannelFactory factory;
51                 OperationContext context;
52
53                 #region delegates
54                 readonly ProcessDelegate _processDelegate;
55
56                 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters);
57
58                 readonly RequestDelegate requestDelegate;
59
60                 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
61
62                 readonly SendDelegate sendDelegate;
63
64                 delegate void SendDelegate (Message msg, TimeSpan timeout);
65                 #endregion
66
67                 public ClientRuntimeChannel (ServiceEndpoint endpoint,
68                         ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
69                         : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
70                 {
71                 }
72
73                 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
74                 {
75                         if (runtime == null)
76                                 throw new ArgumentNullException ("runtime");
77                         this.runtime = runtime;
78                         this.remote_address = remoteAddress;
79                         if (runtime.Via == null)
80                                 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
81                         this.contract = contract;
82                         this.message_version = messageVersion;
83                         default_open_timeout = openTimeout;
84                         default_close_timeout = closeTimeout;
85                         _processDelegate = new ProcessDelegate (Process);
86                         requestDelegate = new RequestDelegate (Request);
87                         sendDelegate = new SendDelegate (Send);
88
89                         // default values
90                         AllowInitializationUI = true;
91                         OperationTimeout = TimeSpan.FromMinutes (1);
92
93                         if (contextChannel != null)
94                                 channel = contextChannel;
95                         else {
96                                 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
97                                 try {
98                                         channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
99                                         this.factory = factory;
100                                 } catch (TargetInvocationException ex) {
101                                         if (ex.InnerException != null)
102                                                 throw ex.InnerException;
103                                         else
104                                                 throw;
105                                 }
106                         }
107                 }
108
109                 public ContractDescription Contract {
110                         get { return contract; }
111                 }
112
113                 public ClientRuntime Runtime {
114                         get { return runtime; }
115                 }
116
117                 IRequestChannel RequestChannel {
118                         get { return channel as IRequestChannel; }
119                 }
120
121                 IOutputChannel OutputChannel {
122                         get { return channel as IOutputChannel; }
123                 }
124
125                 internal IDuplexChannel DuplexChannel {
126                         get { return channel as IDuplexChannel; }
127                 }
128
129                 #region IClientChannel
130
131                 bool did_interactive_initialization;
132
133                 public bool AllowInitializationUI { get; set; }
134
135                 public bool DidInteractiveInitialization {
136                         get { return did_interactive_initialization; }
137                 }
138
139                 public Uri Via {
140                         get { return runtime.Via; }
141                 }
142
143                 class DelegatingWaitHandle : WaitHandle
144                 {
145                         public DelegatingWaitHandle (IAsyncResult [] results)
146                         {
147                                 this.results = results;
148                         }
149
150                         IAsyncResult [] results;
151
152                         protected override void Dispose (bool disposing)
153                         {
154                                 if (disposing)
155                                         foreach (var r in results)
156                                                 r.AsyncWaitHandle.Close ();
157                         }
158
159                         public override bool WaitOne ()
160                         {
161                                 foreach (var r in results)
162                                         r.AsyncWaitHandle.WaitOne ();
163                                 return true;
164                         }
165
166                         public override bool WaitOne (int millisecondsTimeout)
167                         {
168                                 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
169                         }
170
171                         WaitHandle [] ResultWaitHandles {
172                                 get {
173                                         var arr = new WaitHandle [results.Length];
174                                         for (int i = 0; i < arr.Length; i++)
175                                                 arr [i] = results [i].AsyncWaitHandle;
176                                         return arr;
177                                 }
178                         }
179
180 #if !MOONLIGHT
181                         public override bool WaitOne (int millisecondsTimeout, bool exitContext)
182                         {
183                                 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
184                         }
185
186                         public override bool WaitOne (TimeSpan timeout, bool exitContext)
187                         {
188                                 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
189                         }
190 #endif
191                 }
192
193                 class DisplayUIAsyncResult : IAsyncResult
194                 {
195                         public DisplayUIAsyncResult (IAsyncResult [] results)
196                         {
197                                 this.results = results;
198                         }
199
200                         IAsyncResult [] results;
201
202                         internal IAsyncResult [] Results {
203                                 get { return results; }
204                         }
205
206                         public object AsyncState {
207                                 get { return null; }
208                         }
209
210                         WaitHandle wait_handle;
211
212                         public WaitHandle AsyncWaitHandle {
213                                 get {
214                                         if (wait_handle == null)
215                                                 wait_handle = new DelegatingWaitHandle (results);
216                                         return wait_handle;
217                                 }
218                         }
219
220                         public bool CompletedSynchronously {
221                                 get {
222                                         foreach (var r in results)
223                                                 if (!r.CompletedSynchronously)
224                                                         return false;
225                                         return true;
226                                 }
227                         }
228                         public bool IsCompleted {
229                                 get {
230                                         foreach (var r in results)
231                                                 if (!r.IsCompleted)
232                                                         return false;
233                                         return true;
234                                 }
235                         }
236                 }
237
238                 public IAsyncResult BeginDisplayInitializationUI (
239                         AsyncCallback callback, object state)
240                 {
241                         OnInitializationUI ();
242                         IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
243                         int i = 0;
244                         foreach (var init in runtime.InteractiveChannelInitializers)
245                                 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
246                         return new DisplayUIAsyncResult (arr);
247                 }
248
249                 public void EndDisplayInitializationUI (
250                         IAsyncResult result)
251                 {
252                         DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
253                         int i = 0;
254                         foreach (var init in runtime.InteractiveChannelInitializers)
255                                 init.EndDisplayInitializationUI (r.Results [i++]);
256
257                         did_interactive_initialization = true;
258                 }
259
260                 public void DisplayInitializationUI ()
261                 {
262                         OnInitializationUI ();
263                         foreach (var init in runtime.InteractiveChannelInitializers)
264                                 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
265
266                         did_interactive_initialization = true;
267                 }
268
269                 void OnInitializationUI ()
270                 {
271                         if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
272                                 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
273                 }
274
275                 public void Dispose ()
276                 {
277                         Close ();
278                 }
279
280                 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
281
282                 #endregion
283
284                 #region IContextChannel
285
286                 [MonoTODO]
287                 public bool AllowOutputBatching { get; set; }
288
289                 public IInputSession InputSession {
290                         get {
291                                 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
292                                 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
293                                 if (ch != null)
294                                         return ch.Session;
295                                 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
296                                 return dch != null ? dch.Session : null;
297                         }
298                 }
299
300                 public EndpointAddress LocalAddress {
301                         get {
302                                 var dc = OperationChannel as IDuplexChannel;
303                                 return dc != null ? dc.LocalAddress : null;
304                         }
305                 }
306
307                 [MonoTODO]
308                 public TimeSpan OperationTimeout { get; set; }
309
310                 public IOutputSession OutputSession {
311                         get {
312                                 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
313                                 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
314                                 if (ch != null)
315                                         return ch.Session;
316                                 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
317                                 return dch != null ? dch.Session : null;
318                         }
319                 }
320
321                 public EndpointAddress RemoteAddress {
322                         get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
323                 }
324
325                 public string SessionId {
326                         get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
327                 }
328
329                 #endregion
330
331                 // CommunicationObject
332                 protected internal override TimeSpan DefaultOpenTimeout {
333                         get { return default_open_timeout; }
334                 }
335
336                 protected internal override TimeSpan DefaultCloseTimeout {
337                         get { return default_close_timeout; }
338                 }
339
340                 protected override void OnAbort ()
341                 {
342                         channel.Abort ();
343                         if (factory != null) // ... is it valid?
344                                 factory.Abort ();
345                 }
346
347                 Action<TimeSpan> close_delegate;
348
349                 protected override IAsyncResult OnBeginClose (
350                         TimeSpan timeout, AsyncCallback callback, object state)
351                 {
352                         if (close_delegate == null)
353                                 close_delegate = new Action<TimeSpan> (OnClose);
354                         return close_delegate.BeginInvoke (timeout, callback, state);
355                 }
356
357                 protected override void OnEndClose (IAsyncResult result)
358                 {
359                         close_delegate.EndInvoke (result);
360                 }
361
362                 protected override void OnClose (TimeSpan timeout)
363                 {
364                         DateTime start = DateTime.Now;
365                         channel.Close (timeout);
366                 }
367
368                 Action<TimeSpan> open_callback;
369
370                 protected override IAsyncResult OnBeginOpen (
371                         TimeSpan timeout, AsyncCallback callback, object state)
372                 {
373                         if (open_callback == null)
374                                 open_callback = new Action<TimeSpan> (OnOpen);
375                         return open_callback.BeginInvoke (timeout, callback, state);
376                 }
377
378                 protected override void OnEndOpen (IAsyncResult result)
379                 {
380                         if (open_callback == null)
381                                 throw new InvalidOperationException ("Async open operation has not started");
382                         open_callback.EndInvoke (result);
383                 }
384
385                 protected override void OnOpen (TimeSpan timeout)
386                 {
387                         if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
388                                 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
389                         if (channel.State == CommunicationState.Created)
390                                 channel.Open (timeout);
391                 }
392
393                 // IChannel
394
395                 IChannel OperationChannel {
396                         get { return channel; }
397                 }
398
399                 public T GetProperty<T> () where T : class
400                 {
401                         return OperationChannel.GetProperty<T> ();
402                 }
403
404                 // IExtensibleObject<IContextChannel>
405
406                 IExtensionCollection<IContextChannel> extensions;
407
408                 public IExtensionCollection<IContextChannel> Extensions {
409                         get {
410                                 if (extensions == null)
411                                         extensions = new ExtensionCollection<IContextChannel> (this);
412                                 return extensions;
413                         }
414                 }
415
416                 #region Request/Output processing
417
418                 class TempAsyncResult : IAsyncResult
419                 {
420                         public TempAsyncResult (object returnValue, object state)
421                         {
422                                 ReturnValue = returnValue;
423                                 AsyncState = state;
424                                 CompletedSynchronously = true;
425                                 IsCompleted = true;
426                                 AsyncWaitHandle = new ManualResetEvent (true);
427                         }
428                         
429                         public object ReturnValue { get; set; }
430                         public object AsyncState { get; set; }
431                         public bool CompletedSynchronously { get; set; }
432                         public bool IsCompleted { get; set; }
433                         public WaitHandle AsyncWaitHandle { get; set; }
434                 }
435
436                 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
437                 {
438                         if (context != null)
439                                 throw new InvalidOperationException ("another operation is in progress");
440                         context = OperationContext.Current;
441
442                         // FIXME: this is a workaround for bug #633945
443                         switch (Environment.OSVersion.Platform) {
444                         case PlatformID.Unix:
445                         case PlatformID.MacOSX:
446                                 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
447                         default:
448                                 var result = Process (method, operationName, parameters);
449                                 var ret = new TempAsyncResult (result, asyncState);
450                                 if (callback != null)
451                                         callback (ret);
452                                 return ret;
453                         }
454                 }
455
456                 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
457                 {
458                         context = null;
459                         if (result == null)
460                                 throw new ArgumentNullException ("result");
461                         if (parameters == null)
462                                 throw new ArgumentNullException ("parameters");
463                         // FIXME: the method arguments should be verified to be 
464                         // identical to the arguments in the corresponding begin method.
465                         // FIXME: this is a workaround for bug #633945
466                         switch (Environment.OSVersion.Platform) {
467                         case PlatformID.Unix:
468                         case PlatformID.MacOSX:
469                                 return _processDelegate.EndInvoke (result);
470                         default:
471                                 return ((TempAsyncResult) result).ReturnValue;
472                         }
473                 }
474
475                 public object Process (MethodBase method, string operationName, object [] parameters)
476                 {
477                         try {
478                                 return DoProcess (method, operationName, parameters);
479                         } catch (Exception ex) {
480 #if MOONLIGHT // just for debugging
481                                 Console.Write ("Exception in async operation: ");
482                                 Console.WriteLine (ex);
483 #endif
484                                 throw;
485                         }
486                 }
487
488                 object DoProcess (MethodBase method, string operationName, object [] parameters)
489                 {
490                         if (AllowInitializationUI)
491                                 DisplayInitializationUI ();
492                         OperationDescription od = SelectOperation (method, operationName, parameters);
493
494                         if (State != CommunicationState.Opened)
495                                 Open ();
496
497                         if (!od.IsOneWay)
498                                 return Request (od, parameters);
499                         else {
500                                 Output (od, parameters);
501                                 return null;
502                         }
503                 }
504
505                 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
506                 {
507                         string operation;
508                         if (Runtime.OperationSelector != null)
509                                 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
510                         else
511                                 operation = operationName;
512                         OperationDescription od = contract.Operations.Find (operation);
513                         if (od == null)
514                                 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
515                         return od;
516                 }
517
518                 void Output (OperationDescription od, object [] parameters)
519                 {
520                         ClientOperation op = runtime.Operations [od.Name];
521                         Send (CreateRequest (op, parameters), OperationTimeout);
522                 }
523
524                 object Request (OperationDescription od, object [] parameters)
525                 {
526                         ClientOperation op = runtime.Operations [od.Name];
527                         object [] inspections = new object [runtime.MessageInspectors.Count];
528                         Message req = CreateRequest (op, parameters);
529
530                         for (int i = 0; i < inspections.Length; i++)
531                                 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
532
533                         Message res = Request (req, OperationTimeout);
534                         if (res.IsFault) {
535                                 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
536                                 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
537                                 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
538                                 Exception ex;
539                                 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
540                                         if (fault.HasDetail) {
541                                                 Type detailType = typeof (ExceptionDetail);
542                                                 var freader = fault.GetReaderAtDetailContents ();
543                                                 DataContractSerializer ds = null;
544 #if !NET_2_1
545                                                 foreach (var fci in op.FaultContractInfos)
546                                                         if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
547                                                                 detailType = fci.Detail;
548                                                                 ds = fci.Serializer;
549                                                                 break;
550                                                         }
551 #endif
552                                                 if (ds == null)
553                                                         ds = new DataContractSerializer (detailType);
554                                                 var detail = ds.ReadObject (freader);
555                                                 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
556                                         }
557
558                                         if (ex == null)
559                                                 ex = new FaultException (fault);
560                                 }
561                                 throw ex;
562                         }
563
564                         for (int i = 0; i < inspections.Length; i++)
565                                 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
566
567                         if (op.DeserializeReply)
568                                 return op.Formatter.DeserializeReply (res, parameters);
569                         else
570                                 return res;
571                 }
572
573                 #region Message-based Request() and Send()
574                 // They are internal for ClientBase<T>.ChannelBase use.
575                 internal Message Request (Message msg, TimeSpan timeout)
576                 {
577                         if (RequestChannel != null)
578                                 return RequestChannel.Request (msg, timeout);
579                         else {
580                                 DateTime startTime = DateTime.Now;
581                                 OutputChannel.Send (msg, timeout);
582                                 return ((IDuplexChannel) OutputChannel).Receive (timeout - (DateTime.Now - startTime));
583                         }
584                 }
585
586                 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
587                 {
588                         return requestDelegate.BeginInvoke (msg, timeout, callback, state);
589                 }
590
591                 internal Message EndRequest (IAsyncResult result)
592                 {
593                         return requestDelegate.EndInvoke (result);
594                 }
595
596                 internal void Send (Message msg, TimeSpan timeout)
597                 {
598                         if (OutputChannel != null)
599                                 OutputChannel.Send (msg, timeout);
600                         else
601                                 RequestChannel.Request (msg, timeout); // and ignore returned message.
602                 }
603
604                 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
605                 {
606                         return sendDelegate.BeginInvoke (msg, timeout, callback, state);
607                 }
608
609                 internal void EndSend (IAsyncResult result)
610                 {
611                         sendDelegate.EndInvoke (result);
612                 }
613                 #endregion
614
615                 Message CreateRequest (ClientOperation op, object [] parameters)
616                 {
617                         MessageVersion version = message_version;
618                         if (version == null)
619                                 version = MessageVersion.Default;
620
621                         Message msg;
622                         if (op.SerializeRequest)
623                                 msg = op.Formatter.SerializeRequest (
624                                         version, parameters);
625                         else {
626                                 if (parameters.Length != 1)
627                                         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));
628                                 if (!(parameters [0] is Message))
629                                         throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
630                                 msg = (Message) parameters [0];
631                         }
632
633                         context = context ?? OperationContext.Current;
634                         if (context != null) {
635                                 // CopyHeadersFrom does not work here (brings duplicates -> error)
636                                 foreach (var mh in context.OutgoingMessageHeaders) {
637                                         int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
638                                         if (x >= 0)
639                                                 msg.Headers.RemoveAt (x);
640                                         msg.Headers.Add ((MessageHeader) mh);
641                                 }
642                                 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
643                         }
644
645                         // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
646                         //if (OutputSession != null)
647                         //      msg.Headers.MessageId = new UniqueId (OutputSession.Id);
648                         msg.Properties.AllowOutputBatching = AllowOutputBatching;
649
650                         if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
651                                 if (msg.Headers.MessageId == null)
652                                         msg.Headers.MessageId = new UniqueId ();
653                                 if (msg.Headers.ReplyTo == null)
654                                         msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
655                                 if (msg.Headers.To == null && RemoteAddress != null)
656                                         msg.Headers.To = RemoteAddress.Uri;
657                         }
658
659                         return msg;
660                 }
661
662                 #endregion
663         }
664 }