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