New tests.
[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.CreateRuntime (), 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                         this.runtime = runtime;
76                         this.remote_address = remoteAddress;
77                         runtime.Via = via;
78                         this.contract = contract;
79                         this.message_version = messageVersion;
80                         default_open_timeout = openTimeout;
81                         default_close_timeout = closeTimeout;
82                         _processDelegate = new ProcessDelegate (Process);
83                         requestDelegate = new RequestDelegate (Request);
84                         sendDelegate = new SendDelegate (Send);
85
86                         // default values
87                         AllowInitializationUI = true;
88                         OperationTimeout = TimeSpan.FromMinutes (1);
89
90                         if (contextChannel != null)
91                                 channel = contextChannel;
92                         else {
93                                 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
94                                 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
95                                 this.factory = factory;
96                         }
97                 }
98
99                 public ContractDescription Contract {
100                         get { return contract; }
101                 }
102
103                 public ClientRuntime Runtime {
104                         get { return runtime; }
105                 }
106
107                 IRequestChannel RequestChannel {
108                         get { return channel as IRequestChannel; }
109                 }
110
111                 IOutputChannel OutputChannel {
112                         get { return channel as IOutputChannel; }
113                 }
114
115                 internal IDuplexChannel DuplexChannel {
116                         get { return channel as IDuplexChannel; }
117                 }
118
119                 #region IClientChannel
120
121                 bool did_interactive_initialization;
122
123                 public bool AllowInitializationUI { get; set; }
124
125                 public bool DidInteractiveInitialization {
126                         get { return did_interactive_initialization; }
127                 }
128
129                 public Uri Via {
130                         get { return runtime.Via; }
131                 }
132
133                 class DelegatingWaitHandle : WaitHandle
134                 {
135                         public DelegatingWaitHandle (IAsyncResult [] results)
136                         {
137                                 this.results = results;
138                         }
139
140                         IAsyncResult [] results;
141
142                         protected override void Dispose (bool disposing)
143                         {
144                                 if (disposing)
145                                         foreach (var r in results)
146                                                 r.AsyncWaitHandle.Close ();
147                         }
148
149                         public override bool WaitOne ()
150                         {
151                                 foreach (var r in results)
152                                         r.AsyncWaitHandle.WaitOne ();
153                                 return true;
154                         }
155
156                         public override bool WaitOne (int millisecondsTimeout)
157                         {
158                                 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
159                         }
160
161                         WaitHandle [] ResultWaitHandles {
162                                 get {
163                                         var arr = new WaitHandle [results.Length];
164                                         for (int i = 0; i < arr.Length; i++)
165                                                 arr [i] = results [i].AsyncWaitHandle;
166                                         return arr;
167                                 }
168                         }
169
170 #if !MOONLIGHT
171                         public override bool WaitOne (int millisecondsTimeout, bool exitContext)
172                         {
173                                 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
174                         }
175
176                         public override bool WaitOne (TimeSpan timeout, bool exitContext)
177                         {
178                                 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
179                         }
180 #endif
181                 }
182
183                 class DisplayUIAsyncResult : IAsyncResult
184                 {
185                         public DisplayUIAsyncResult (IAsyncResult [] results)
186                         {
187                                 this.results = results;
188                         }
189
190                         IAsyncResult [] results;
191
192                         internal IAsyncResult [] Results {
193                                 get { return results; }
194                         }
195
196                         public object AsyncState {
197                                 get { return null; }
198                         }
199
200                         WaitHandle wait_handle;
201
202                         public WaitHandle AsyncWaitHandle {
203                                 get {
204                                         if (wait_handle == null)
205                                                 wait_handle = new DelegatingWaitHandle (results);
206                                         return wait_handle;
207                                 }
208                         }
209
210                         public bool CompletedSynchronously {
211                                 get {
212                                         foreach (var r in results)
213                                                 if (!r.CompletedSynchronously)
214                                                         return false;
215                                         return true;
216                                 }
217                         }
218                         public bool IsCompleted {
219                                 get {
220                                         foreach (var r in results)
221                                                 if (!r.IsCompleted)
222                                                         return false;
223                                         return true;
224                                 }
225                         }
226                 }
227
228                 public IAsyncResult BeginDisplayInitializationUI (
229                         AsyncCallback callback, object state)
230                 {
231                         OnInitializationUI ();
232                         IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
233                         int i = 0;
234                         foreach (var init in runtime.InteractiveChannelInitializers)
235                                 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
236                         return new DisplayUIAsyncResult (arr);
237                 }
238
239                 public void EndDisplayInitializationUI (
240                         IAsyncResult result)
241                 {
242                         DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
243                         int i = 0;
244                         foreach (var init in runtime.InteractiveChannelInitializers)
245                                 init.EndDisplayInitializationUI (r.Results [i++]);
246
247                         did_interactive_initialization = true;
248                 }
249
250                 public void DisplayInitializationUI ()
251                 {
252                         OnInitializationUI ();
253                         foreach (var init in runtime.InteractiveChannelInitializers)
254                                 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
255
256                         did_interactive_initialization = true;
257                 }
258
259                 void OnInitializationUI ()
260                 {
261                         if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
262                                 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
263                 }
264
265                 public void Dispose ()
266                 {
267                         Close ();
268                 }
269
270                 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
271
272                 #endregion
273
274                 #region IContextChannel
275
276                 [MonoTODO]
277                 public bool AllowOutputBatching { get; set; }
278
279                 public IInputSession InputSession {
280                         get {
281                                 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
282                                 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
283                                 if (ch != null)
284                                         return ch.Session;
285                                 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
286                                 return dch != null ? dch.Session : null;
287                         }
288                 }
289
290                 public EndpointAddress LocalAddress {
291                         get {
292                                 var dc = OperationChannel as IDuplexChannel;
293                                 return dc != null ? dc.LocalAddress : null;
294                         }
295                 }
296
297                 [MonoTODO]
298                 public TimeSpan OperationTimeout { get; set; }
299
300                 public IOutputSession OutputSession {
301                         get {
302                                 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
303                                 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
304                                 if (ch != null)
305                                         return ch.Session;
306                                 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
307                                 return dch != null ? dch.Session : null;
308                         }
309                 }
310
311                 public EndpointAddress RemoteAddress {
312                         get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
313                 }
314
315                 public string SessionId {
316                         get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
317                 }
318
319                 #endregion
320
321                 // CommunicationObject
322                 protected internal override TimeSpan DefaultOpenTimeout {
323                         get { return default_open_timeout; }
324                 }
325
326                 protected internal override TimeSpan DefaultCloseTimeout {
327                         get { return default_close_timeout; }
328                 }
329
330                 protected override void OnAbort ()
331                 {
332                         channel.Abort ();
333                         if (factory != null) // ... is it valid?
334                                 factory.Abort ();
335                 }
336
337                 Action<TimeSpan> close_delegate;
338
339                 protected override IAsyncResult OnBeginClose (
340                         TimeSpan timeout, AsyncCallback callback, object state)
341                 {
342                         if (close_delegate == null)
343                                 close_delegate = new Action<TimeSpan> (OnClose);
344                         return close_delegate.BeginInvoke (timeout, callback, state);
345                 }
346
347                 protected override void OnEndClose (IAsyncResult result)
348                 {
349                         close_delegate.EndInvoke (result);
350                 }
351
352                 protected override void OnClose (TimeSpan timeout)
353                 {
354                         DateTime start = DateTime.Now;
355                         channel.Close (timeout);
356                 }
357
358                 Action<TimeSpan> open_callback;
359
360                 protected override IAsyncResult OnBeginOpen (
361                         TimeSpan timeout, AsyncCallback callback, object state)
362                 {
363                         if (open_callback == null)
364                                 open_callback = new Action<TimeSpan> (OnOpen);
365                         return open_callback.BeginInvoke (timeout, callback, state);
366                 }
367
368                 protected override void OnEndOpen (IAsyncResult result)
369                 {
370                         if (open_callback == null)
371                                 throw new InvalidOperationException ("Async open operation has not started");
372                         open_callback.EndInvoke (result);
373                 }
374
375                 protected override void OnOpen (TimeSpan timeout)
376                 {
377                         if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
378                                 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
379                         if (channel.State == CommunicationState.Created)
380                                 channel.Open (timeout);
381                 }
382
383                 // IChannel
384
385                 IChannel OperationChannel {
386                         get { return channel; }
387                 }
388
389                 public T GetProperty<T> () where T : class
390                 {
391                         return OperationChannel.GetProperty<T> ();
392                 }
393
394                 // IExtensibleObject<IContextChannel>
395
396                 IExtensionCollection<IContextChannel> extensions;
397
398                 public IExtensionCollection<IContextChannel> Extensions {
399                         get {
400                                 if (extensions == null)
401                                         extensions = new ExtensionCollection<IContextChannel> (this);
402                                 return extensions;
403                         }
404                 }
405
406                 #region Request/Output processing
407
408                 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
409                 {
410                         if (context != null)
411                                 throw new InvalidOperationException ("another operation is in progress");
412                         context = OperationContext.Current;
413                         return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
414                 }
415
416                 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
417                 {
418                         context = null;
419                         if (result == null)
420                                 throw new ArgumentNullException ("result");
421                         if (parameters == null)
422                                 throw new ArgumentNullException ("parameters");
423                         // FIXME: the method arguments should be verified to be 
424                         // identical to the arguments in the corresponding begin method.
425                         return _processDelegate.EndInvoke (result);
426                 }
427
428                 public object Process (MethodBase method, string operationName, object [] parameters)
429                 {
430                         try {
431                                 return DoProcess (method, operationName, parameters);
432                         } catch (Exception ex) {
433 #if MOONLIGHT // just for debugging
434                                 Console.Write ("Exception in async operation: ");
435                                 Console.WriteLine (ex);
436 #endif
437                                 throw;
438                         }
439                 }
440
441                 object DoProcess (MethodBase method, string operationName, object [] parameters)
442                 {
443                         if (AllowInitializationUI)
444                                 DisplayInitializationUI ();
445                         OperationDescription od = SelectOperation (method, operationName, parameters);
446
447                         if (State != CommunicationState.Opened)
448                                 Open ();
449
450                         if (!od.IsOneWay)
451                                 return Request (od, parameters);
452                         else {
453                                 Output (od, parameters);
454                                 return null;
455                         }
456                 }
457
458                 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
459                 {
460                         string operation;
461                         if (Runtime.OperationSelector != null)
462                                 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
463                         else
464                                 operation = operationName;
465                         OperationDescription od = contract.Operations.Find (operation);
466                         if (od == null)
467                                 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
468                         return od;
469                 }
470
471                 void Output (OperationDescription od, object [] parameters)
472                 {
473                         ClientOperation op = runtime.Operations [od.Name];
474                         Send (CreateRequest (op, parameters), OperationTimeout);
475                 }
476
477                 object Request (OperationDescription od, object [] parameters)
478                 {
479                         ClientOperation op = runtime.Operations [od.Name];
480                         object [] inspections = new object [runtime.MessageInspectors.Count];
481                         Message req = CreateRequest (op, parameters);
482
483                         for (int i = 0; i < inspections.Length; i++)
484                                 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
485
486                         Message res = Request (req, OperationTimeout);
487                         if (res.IsFault) {
488                                 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
489                                 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
490                                 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
491                                 Exception ex;
492                                 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
493                                         if (fault.HasDetail) {
494                                                 Type detailType = typeof (ExceptionDetail);
495                                                 var freader = fault.GetReaderAtDetailContents ();
496                                                 DataContractSerializer ds = null;
497 #if !NET_2_1
498                                                 foreach (var fci in op.FaultContractInfos)
499                                                         if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
500                                                                 detailType = fci.Detail;
501                                                                 ds = fci.Serializer;
502                                                                 break;
503                                                         }
504 #endif
505                                                 if (ds == null)
506                                                         ds = new DataContractSerializer (detailType);
507                                                 var detail = ds.ReadObject (freader);
508                                                 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
509                                         }
510
511                                         if (ex == null)
512                                                 ex = new FaultException (fault);
513                                 }
514                                 throw ex;
515                         }
516
517                         for (int i = 0; i < inspections.Length; i++)
518                                 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
519
520                         if (op.DeserializeReply)
521                                 return op.GetFormatter ().DeserializeReply (res, parameters);
522                         else
523                                 return res;
524                 }
525
526                 #region Message-based Request() and Send()
527                 // They are internal for ClientBase<T>.ChannelBase use.
528                 internal Message Request (Message msg, TimeSpan timeout)
529                 {
530                         if (RequestChannel != null)
531                                 return RequestChannel.Request (msg, timeout);
532                         else {
533                                 DateTime startTime = DateTime.Now;
534                                 OutputChannel.Send (msg, timeout);
535                                 return ((IDuplexChannel) OutputChannel).Receive (timeout - (DateTime.Now - startTime));
536                         }
537                 }
538
539                 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
540                 {
541                         return requestDelegate.BeginInvoke (msg, timeout, callback, state);
542                 }
543
544                 internal Message EndRequest (IAsyncResult result)
545                 {
546                         return requestDelegate.EndInvoke (result);
547                 }
548
549                 internal void Send (Message msg, TimeSpan timeout)
550                 {
551                         OutputChannel.Send (msg, timeout);
552                 }
553
554                 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
555                 {
556                         return sendDelegate.BeginInvoke (msg, timeout, callback, state);
557                 }
558
559                 internal void EndSend (IAsyncResult result)
560                 {
561                         sendDelegate.EndInvoke (result);
562                 }
563                 #endregion
564
565                 Message CreateRequest (ClientOperation op, object [] parameters)
566                 {
567                         MessageVersion version = message_version;
568                         if (version == null)
569                                 version = MessageVersion.Default;
570
571                         Message msg;
572                         if (op.SerializeRequest)
573                                 msg = op.GetFormatter ().SerializeRequest (
574                                         version, parameters);
575                         else {
576                                 if (parameters.Length != 1)
577                                         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));
578                                 if (!(parameters [0] is Message))
579                                         throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
580                                 msg = (Message) parameters [0];
581                         }
582
583                         context = context ?? OperationContext.Current;
584                         if (context != null) {
585                                 // CopyHeadersFrom does not work here (brings duplicates -> error)
586                                 foreach (var mh in context.OutgoingMessageHeaders) {
587                                         int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
588                                         if (x >= 0)
589                                                 msg.Headers.RemoveAt (x);
590                                         msg.Headers.Add ((MessageHeader) mh);
591                                 }
592                                 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
593                         }
594
595                         if (OutputSession != null)
596                                 msg.Headers.MessageId = new UniqueId (OutputSession.Id);
597                         msg.Properties.AllowOutputBatching = AllowOutputBatching;
598
599                         if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
600                                 if (msg.Headers.MessageId == null)
601                                         msg.Headers.MessageId = new UniqueId ();
602                                 if (msg.Headers.ReplyTo == null)
603                                         msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
604                                 if (msg.Headers.To == null)
605                                         msg.Headers.To = RemoteAddress.Uri;
606                         }
607
608                         return msg;
609                 }
610
611                 #endregion
612         }
613 }