column name and ordinal fix...tested on 10.1
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / MessageFault.cs
1 //
2 // MessageFault.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005-2009 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.Collections.Generic;
30 using System.IO;
31 using System.Runtime.Serialization;
32 using System.Xml;
33
34 namespace System.ServiceModel.Channels
35 {
36         public abstract class MessageFault
37         {
38                 // type members
39
40                 public static MessageFault CreateFault (Message message, int maxBufferSize)
41                 {
42                         try {
43                                 if (message.Version.Envelope == EnvelopeVersion.Soap11)
44                                         return CreateFault11 (message, maxBufferSize);
45                                 else // common to None and SOAP12
46                                         return CreateFault12 (message, maxBufferSize);
47                         } catch (XmlException ex) {
48                                 throw new CommunicationException ("Received an invalid SOAP Fault message", ex);
49                         }
50                 }
51
52                 static MessageFault CreateFault11 (Message message, int maxBufferSize)
53                 {
54                         FaultCode fc = null;
55                         FaultReason fr = null;
56                         object details = null;
57                         XmlDictionaryReader r = message.GetReaderAtBodyContents ();
58                         r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
59                         r.MoveToContent ();
60
61                         while (r.NodeType != XmlNodeType.EndElement) {
62                                 switch (r.LocalName) {
63                                 case "faultcode":
64                                         fc = ReadFaultCode11 (r);
65                                         break;
66                                 case "faultstring":
67                                         fr = new FaultReason (r.ReadElementContentAsString());
68                                         break;
69                                 case "detail":
70                                         return new XmlReaderDetailMessageFault (message, r, fc, fr, null, null);
71                                 case "faultactor":
72                                 default:
73                                         throw new NotImplementedException ();
74                                 }
75                                 r.MoveToContent ();
76                         }
77                         r.ReadEndElement ();
78
79                         if (fr == null)
80                                 throw new XmlException ("Reason is missing in the Fault message");
81
82                         if (details == null)
83                                 return CreateFault (fc, fr);
84                         return CreateFault (fc, fr, details);
85                 }
86
87                 static MessageFault CreateFault12 (Message message, int maxBufferSize)
88                 {
89                         FaultCode fc = null;
90                         FaultReason fr = null;
91                         string node = null;
92                         XmlDictionaryReader r = message.GetReaderAtBodyContents ();
93                         r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
94
95                         for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) {
96                                 if (r.NamespaceURI != message.Version.Envelope.Namespace) {
97                                         r.Skip ();
98                                         continue;
99                                 }
100                                 switch (r.LocalName) {
101                                 case "Code":
102                                         fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace);
103                                         break;
104                                 case "Reason":
105                                         fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace);
106                                         break;
107                                 case "Node":
108                                         node = r.ReadElementContentAsString ();
109                                         break;
110                                 case "Role":
111                                         r.Skip (); // no corresponding member to store.
112                                         break;
113                                 case "Detail":
114                                         if (!r.IsEmptyElement)
115                                                 return new XmlReaderDetailMessageFault (message, r, fc, fr, null, node);
116                                         r.Read ();
117                                         break;
118                                 default:
119                                         throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
120                                 }
121                         }
122
123                         if (fr == null)
124                                 throw new XmlException ("Reason is missing in the Fault message");
125
126                         r.ReadEndElement ();
127
128                         return new SimpleMessageFault (fc, fr, false, null, null, null, node);
129                 }
130
131                 static FaultCode ReadFaultCode11 (XmlDictionaryReader r)
132                 {
133                         FaultCode subcode = null;
134                         XmlQualifiedName value = XmlQualifiedName.Empty;
135
136                         if (r.IsEmptyElement)
137                                 throw new ArgumentException ("Fault Code is mandatory in SOAP fault message.");
138
139                         r.ReadStartElement ("faultcode");
140                         r.MoveToContent ();
141                         while (r.NodeType != XmlNodeType.EndElement) {
142                                 if (r.NodeType == XmlNodeType.Element)
143                                         subcode = ReadFaultCode11 (r);
144                                 else
145                                         value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver);
146                                 r.MoveToContent ();
147                         }
148                         r.ReadEndElement ();
149
150                         return new FaultCode (value.Name, value.Namespace, subcode);
151                 }
152
153                 static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns)
154                 {
155                         FaultCode subcode = null;
156                         XmlQualifiedName value = XmlQualifiedName.Empty;
157
158                         if (r.IsEmptyElement)
159                                 throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code.");
160
161                         r.ReadStartElement (); // could be either Code or SubCode
162                         r.MoveToContent ();
163                         while (r.NodeType != XmlNodeType.EndElement) {
164                                 switch (r.LocalName) {
165                                 case "Subcode":
166                                         subcode = ReadFaultCode12 (r, ns);
167                                         break;
168                                 case "Value":
169                                         value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns);
170                                         break;
171                                 default:
172                                         throw new ArgumentException (String.Format ("Unexpected Fault Code subelement: '{0}'", r.LocalName));
173                                 }
174                                 r.MoveToContent ();
175                         }
176                         r.ReadEndElement ();
177
178                         return new FaultCode (value.Name, value.Namespace, subcode);
179                 }
180
181                 static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns)
182                 {
183                         List<FaultReasonText> l = new List<FaultReasonText> ();
184                         if (r.IsEmptyElement)
185                                 throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text.");
186
187                         r.ReadStartElement ("Reason", ns);
188                         for (r.MoveToContent ();
189                              r.NodeType != XmlNodeType.EndElement;
190                              r.MoveToContent ()) {
191                                 string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace");
192                                 if (lang == null)
193                                         throw new XmlException ("xml:lang is mandatory on fault reason Text");
194                                 l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang));
195                         }
196                         r.ReadEndElement ();
197
198                         return new FaultReason (l);
199                 }
200
201                 public static MessageFault CreateFault (FaultCode code,
202                         string reason)
203                 {
204                         return CreateFault (code, new FaultReason (reason));
205                 }
206
207                 public static MessageFault CreateFault (FaultCode code,
208                         FaultReason reason)
209                 {
210                         return new SimpleMessageFault (code, reason,
211                                  false, null, null, null, null);
212                 }
213
214                 public static MessageFault CreateFault (FaultCode code,
215                         FaultReason reason, object detail)
216                 {
217                         return new SimpleMessageFault (code, reason,
218                                 true, detail, new DataContractSerializer (detail.GetType ()), null, null);
219                 }
220
221                 public static MessageFault CreateFault (FaultCode code,
222                         FaultReason reason, object detail,
223                         XmlObjectSerializer formatter)
224                 {
225                         return new SimpleMessageFault (code, reason, true,
226                                 detail, formatter, String.Empty, String.Empty);
227                 }
228
229                 public static MessageFault CreateFault (FaultCode code,
230                         FaultReason reason, object detail,
231                         XmlObjectSerializer formatter, string actor)
232                 {
233                         return new SimpleMessageFault (code, reason,
234                                 true, detail, formatter, actor, String.Empty);
235                 }
236
237                 public static MessageFault CreateFault (FaultCode code,
238                         FaultReason reason, object detail,
239                         XmlObjectSerializer formatter, string actor, string node)
240                 {
241                         return new SimpleMessageFault (code, reason,
242                                 true, detail, formatter, actor, node);
243                 }
244
245                 // pretty simple implementation class
246                 internal abstract class BaseMessageFault : MessageFault
247                 {
248                         string actor, node;
249                         FaultCode code;
250                         FaultReason reason;
251
252                         protected BaseMessageFault (FaultCode code, FaultReason reason, string actor, string node)
253                         {
254                                 this.code = code;
255                                 this.reason = reason;
256                                 this.actor = actor;
257                                 this.node = node;
258                         }
259
260                         public override string Actor {
261                                 get { return actor; }
262                         }
263
264                         public override FaultCode Code {
265                                 get { return code; }
266                         }
267
268                         public override string Node {
269                                 get { return node; }
270                         }
271
272                         public override FaultReason Reason {
273                                 get { return reason; }
274                         }
275                 }
276
277                 internal class SimpleMessageFault : BaseMessageFault
278                 {
279                         bool has_detail;
280                         object detail;
281                         XmlObjectSerializer formatter;
282
283                         public SimpleMessageFault (FaultCode code,
284                                 FaultReason reason, bool has_detail,
285                                 object detail, XmlObjectSerializer formatter,
286                                 string actor, string node)
287                                 : this (code, reason, detail, formatter, actor, node)
288                         {
289                                 this.has_detail = has_detail;
290                         }
291
292                         public SimpleMessageFault (FaultCode code,
293                                 FaultReason reason,
294                                 object detail, XmlObjectSerializer formatter,
295                                 string actor, string node)
296                                 : base (code, reason, actor, node)
297                         {
298                                 if (code == null)
299                                         throw new ArgumentNullException ("code");
300                                 if (reason == null)
301                                         throw new ArgumentNullException ("reason");
302
303                                 this.detail = detail;
304                                 this.formatter = formatter;
305                         }
306
307                         public override bool HasDetail {
308                                 // it is not simply "detail != null" since
309                                 // null detail could become <ms:anyType xsi:nil="true" />
310                                 get { return has_detail; }
311                         }
312
313                         protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
314                         {
315                                 if (formatter == null && detail != null)
316                                         formatter = new DataContractSerializer (detail.GetType ());
317                                 if (formatter != null)
318                                         formatter.WriteObject (writer, detail);
319                                 else
320                                         throw new InvalidOperationException ("There is no fault detail to write");
321                         }
322
323                         public object Detail {
324                                 get { return detail; }
325                         }
326                 }
327
328                 class XmlReaderDetailMessageFault : BaseMessageFault
329                 {
330                         XmlDictionaryReader reader;
331                         bool consumed;
332                         bool has_detail;
333
334                         public XmlReaderDetailMessageFault (Message message, XmlDictionaryReader reader, FaultCode code, FaultReason reason, string actor, string node)
335                                 : base (code, reason, actor, node)
336                         {
337                                 this.reader = reader;
338                                 if (reader.IsEmptyElement)
339                                         has_detail = false;
340                                 reader.MoveToContent ();
341                                 reader.ReadStartElement (); // consume the wrapper
342                                 reader.MoveToContent ();
343                                 has_detail = reader.NodeType != XmlNodeType.EndElement;
344                         }
345
346                         void Consume ()
347                         {
348                                 if (consumed)
349                                         throw new InvalidOperationException ("The fault detail content is already consumed");
350                                 consumed = true;
351                         }
352
353                         public override bool HasDetail {
354                                 get { return has_detail; }
355                         }
356
357                         protected override XmlDictionaryReader OnGetReaderAtDetailContents ()
358                         {
359                                 Consume ();
360                                 return reader;
361                         }
362
363                         protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
364                         {
365                                 if (!HasDetail)
366                                         throw new InvalidOperationException ("There is no fault detail to write");
367                                 Consume ();
368                                 while (reader.NodeType != XmlNodeType.EndElement)
369                                         writer.WriteNode (reader, false);
370                         }
371                 }
372
373                 // instance members
374
375                 protected MessageFault ()
376                 {
377                 }
378
379                 [MonoTODO ("is this true?")]
380                 public virtual string Actor {
381                         get { return String.Empty; }
382                 }
383
384                 public abstract FaultCode Code { get; }
385
386                 public abstract bool HasDetail { get; }
387
388                 [MonoTODO ("is this true?")]
389                 public virtual string Node {
390                         get { return String.Empty; }
391                 }
392
393                 public abstract FaultReason Reason { get; }
394
395                 public T GetDetail<T> ()
396                 {
397                         return GetDetail<T> (new DataContractSerializer (typeof (T)));
398                 }
399
400                 public T GetDetail<T> (XmlObjectSerializer serializer)
401                 {
402                         if (!HasDetail)
403                                 throw new InvalidOperationException ("This message does not have details.");
404
405                         return (T) serializer.ReadObject (GetReaderAtDetailContents ());
406                 }
407
408                 public XmlDictionaryReader GetReaderAtDetailContents ()
409                 {
410                         return OnGetReaderAtDetailContents ();
411                 }
412
413                 public void WriteTo (XmlDictionaryWriter writer,
414                         EnvelopeVersion version)
415                 {
416                         writer.WriteStartElement ("Fault", version.Namespace);
417                         WriteFaultCode (writer, version, Code, false);
418                         WriteReason (writer, version);
419                         if (HasDetail)
420                                 OnWriteDetail (writer, version);
421                         writer.WriteEndElement ();
422                 }
423
424                 private void WriteFaultCode (XmlDictionaryWriter writer, 
425                         EnvelopeVersion version, FaultCode code, bool sub)
426                 {
427                         if (version == EnvelopeVersion.Soap11) {
428                                 writer.WriteStartElement ("", "faultcode", String.Empty);
429                                 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
430                                         writer.WriteXmlnsAttribute ("a", code.Namespace);
431                                 writer.WriteQualifiedName (code.Name, code.Namespace);
432                                 writer.WriteEndElement ();
433                         } else { // Soap12
434                                 writer.WriteStartElement (sub ? "Subcode" : "Code", version.Namespace);
435                                 writer.WriteStartElement ("Value", version.Namespace);
436                                 if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
437                                         writer.WriteXmlnsAttribute ("a", code.Namespace);
438                                 writer.WriteQualifiedName (code.Name, code.Namespace);
439                                 writer.WriteEndElement ();
440                                 if (code.SubCode != null)
441                                         WriteFaultCode (writer, version, code.SubCode, true);
442                                 writer.WriteEndElement ();
443                         }
444                 }
445
446                 private void WriteReason (XmlDictionaryWriter writer, 
447                         EnvelopeVersion version)
448                 {
449                         if (version == EnvelopeVersion.Soap11) {
450                                 foreach (FaultReasonText t in Reason.Translations) {
451                                         writer.WriteStartElement ("", "faultstring", String.Empty);
452                                         if (t.XmlLang != null)
453                                                 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
454                                         writer.WriteString (t.Text);
455                                         writer.WriteEndElement ();
456                                 }
457                         } else { // Soap12
458                                 writer.WriteStartElement ("Reason", version.Namespace);
459                                 foreach (FaultReasonText t in Reason.Translations) {
460                                         writer.WriteStartElement ("Text", version.Namespace);
461                                         if (t.XmlLang != null)
462                                                 writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
463                                         writer.WriteString (t.Text);
464                                         writer.WriteEndElement ();
465                                 }
466                                 writer.WriteEndElement ();
467                         }
468                 }
469
470                 public void WriteTo (XmlWriter writer, EnvelopeVersion version)
471                 {
472                         WriteTo (XmlDictionaryWriter.CreateDictionaryWriter (
473                                 writer), version);
474                 }
475
476                 protected virtual XmlDictionaryReader OnGetReaderAtDetailContents ()
477                 {
478                         if (!HasDetail)
479                                 throw new InvalidOperationException ("There is no fault detail to read");
480                         MemoryStream ms = new MemoryStream ();
481                         using (XmlDictionaryWriter dw =
482                                 XmlDictionaryWriter.CreateDictionaryWriter (
483                                         XmlWriter.Create (ms))) {
484                                 OnWriteDetailContents (dw);
485                         }
486                         ms.Seek (0, SeekOrigin.Begin);
487                         return XmlDictionaryReader.CreateDictionaryReader (
488                                 XmlReader.Create (ms));
489                 }
490
491                 protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
492                 {
493                         OnWriteStartDetail (writer, version);
494                         OnWriteDetailContents (writer);
495                         writer.WriteEndElement ();
496                 }
497
498                 protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
499                 {
500                         if (version == EnvelopeVersion.Soap11)
501                                 writer.WriteStartElement ("detail", String.Empty);
502                         else // Soap12
503                                 writer.WriteStartElement ("Detail", version.Namespace);
504                 }
505
506                 protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer);
507         }
508 }