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