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