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