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