* SoapClientFormatterSink.cs: The deserialized message can be actually a
[mono.git] / mcs / class / System.Runtime.Remoting / System.Runtime.Remoting.Channels / SoapMessageFormatter.cs
1 // created on 03/04/2003 at 14:09\r
2 // \r
3 // System.Runtime.Remoting.Channels.SoapMessageFormatter\r
4 //\r
5 // Author:      Jean-Marc Andre (jean-marc.andre@polymtl.ca)\r
6 //\r
7 //\r
8 \r
9 using System;\r
10 using System.Collections;\r
11 using System.Reflection;\r
12 using System.Runtime.Remoting.Messaging;\r
13 using System.Runtime.Serialization;\r
14 using System.Runtime.Serialization.Formatters;\r
15 \r
16 \r
17 \r
18 namespace System.Runtime.Remoting.Channels {\r
19         enum RemMessageType {\r
20                 MethodCall, MethodResponse, ServerFault, NotRecognize\r
21         }\r
22         \r
23         internal class SoapMessageFormatter {\r
24                 private static FieldInfo _serverFaultExceptionField;\r
25                 private Type _serverType;\r
26                 private MethodInfo _methodCallInfo;\r
27                 private ParameterInfo[] _methodCallParameters;\r
28                 private string _xmlNamespace;\r
29                 \r
30                 static SoapMessageFormatter() {\r
31                         // Get the ServerFault exception field FieldInfo that\r
32                         // will be used later if an exception occurs on the server\r
33                         MemberInfo[] mi = FormatterServices.GetSerializableMembers(typeof(ServerFault), new StreamingContext(StreamingContextStates.All));\r
34                         FieldInfo fi;\r
35                         for(int i = 0; i < mi.Length; i++){\r
36                                 fi = mi[i] as FieldInfo;\r
37                                 if(fi != null && fi.FieldType == typeof(Exception)){\r
38                                         _serverFaultExceptionField = fi; \r
39                                 }\r
40                         }\r
41                         \r
42                 }\r
43                 \r
44                 internal SoapMessageFormatter() {\r
45                         \r
46                 }\r
47                 \r
48                 internal IMessage FormatFault (SoapFault fault, IMethodCallMessage mcm)\r
49                 {\r
50                         ServerFault sf = fault.Detail as ServerFault;\r
51                         Exception e = null;\r
52                         \r
53                         if (sf != null) {\r
54                                 if(_serverFaultExceptionField != null)\r
55                                         e = (Exception) _serverFaultExceptionField.GetValue(sf);\r
56                         }\r
57                         if (e == null)\r
58                                 e = new RemotingException (fault.FaultString);\r
59 \r
60                         return new ReturnMessage((System.Exception)e, mcm);\r
61                 }\r
62                 \r
63                 // used by the client\r
64                 internal IMessage FormatResponse(ISoapMessage soapMsg, IMethodCallMessage mcm) \r
65                 {\r
66                         IMessage rtnMsg;\r
67                         \r
68                         if(soapMsg.MethodName == "Fault") {\r
69                                 // an exception was thrown by the server\r
70                                 Exception e = new SerializationException();\r
71                                 int i = Array.IndexOf(soapMsg.ParamNames, "detail");\r
72                                 if(_serverFaultExceptionField != null)\r
73                                         // todo: revue this 'cause it's not safe\r
74                                         e = (Exception) _serverFaultExceptionField.GetValue(\r
75                                                         soapMsg.ParamValues[i]);\r
76                                 \r
77                                 rtnMsg = new ReturnMessage((System.Exception)e, mcm);\r
78                         }\r
79                         else {\r
80                                 object rtnObject = null;\r
81                                 RemMessageType messageType;\r
82                                 \r
83                                 // Get the output of the function if it is not *void*\r
84                                 if(_methodCallInfo.ReturnType != typeof(void)){\r
85                                         int index = Array.IndexOf(soapMsg.ParamNames, "return");\r
86                                         rtnObject = soapMsg.ParamValues[index];\r
87                                         if(rtnObject is IConvertible) \r
88                                                 rtnObject = Convert.ChangeType(\r
89                                                                 rtnObject, \r
90                                                                 _methodCallInfo.ReturnType);\r
91                                 }\r
92                                 \r
93                                 object[] outParams = new object [_methodCallParameters.Length];\r
94                                 int n=0;\r
95                                 \r
96                                 // check if there are *out* parameters\r
97                                 foreach(ParameterInfo paramInfo in _methodCallParameters) {\r
98                                         \r
99                                         if(paramInfo.ParameterType.IsByRef || paramInfo.IsOut) {\r
100                                                 int index = Array.IndexOf(soapMsg.ParamNames, paramInfo.Name);\r
101                                                 object outParam = soapMsg.ParamValues[index];\r
102                                                 if(outParam is IConvertible)\r
103                                                         outParam = Convert.ChangeType (outParam, paramInfo.ParameterType.GetElementType());\r
104                                                 outParams[n] = outParam;\r
105                                         }\r
106                                         else\r
107                                                 outParams [n] = null;\r
108                                         n++;\r
109                                 }\r
110                                 \r
111                                 rtnMsg = new ReturnMessage (rtnObject, outParams, outParams.Length, mcm.LogicalCallContext, mcm);\r
112                         }\r
113                         return rtnMsg;\r
114                 }\r
115                 \r
116                 // used by the client\r
117                 internal SoapMessage BuildSoapMessageFromMethodCall(\r
118                                 IMethodCallMessage mcm,\r
119                                 out ITransportHeaders requestHeaders)\r
120                 {\r
121                         \r
122                         requestHeaders = new TransportHeaders();\r
123                         SoapMessage soapMsg = new SoapMessage();\r
124                         string uri = mcm.Uri;\r
125 \r
126                         GetInfoFromMethodCallMessage(mcm);\r
127 \r
128                         // Format the SoapMessage that will be used to create the RPC\r
129                         soapMsg.MethodName = mcm.MethodName;\r
130                         int count = mcm.ArgCount;\r
131                         ArrayList paramNames = new ArrayList(_methodCallParameters.Length);\r
132                         ArrayList paramTypes = new ArrayList(_methodCallParameters.Length);\r
133                         ArrayList paramValues = new ArrayList(_methodCallParameters.Length);\r
134                         \r
135                         // Add the function parameters to the SoapMessage class\r
136                         foreach(ParameterInfo paramInfo in _methodCallParameters) {\r
137                                 if (!(paramInfo.IsOut && paramInfo.ParameterType.IsByRef)) {\r
138                                         Type t = paramInfo.ParameterType;\r
139                                         if (t.IsByRef) t = t.GetElementType ();\r
140                                         paramNames.Add(paramInfo.Name);\r
141                                         paramTypes.Add(t);\r
142                                         paramValues.Add(mcm.Args[paramInfo.Position]);\r
143                                 }\r
144                         }                       \r
145                         soapMsg.ParamNames = (string[]) paramNames.ToArray(typeof(string));\r
146                         soapMsg.ParamTypes = (Type[]) paramTypes.ToArray(typeof(Type));\r
147                         soapMsg.ParamValues = (object[]) paramValues.ToArray(typeof(object));\r
148                         soapMsg.XmlNameSpace = SoapServices.GetXmlNamespaceForMethodCall(_methodCallInfo);    \r
149                         \r
150                         // Format the transport headers\r
151                         requestHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";\r
152                         requestHeaders["SOAPAction"] = "\""+\r
153                                 SoapServices.GetSoapActionFromMethodBase(_methodCallInfo)+"\""; \r
154                         requestHeaders[CommonTransportKeys.RequestUri] = mcm.Uri;\r
155                         \r
156                         return soapMsg;\r
157                         \r
158                 }\r
159                 \r
160                 // used by the server\r
161                 internal IMessage BuildMethodCallFromSoapMessage(SoapMessage soapMessage, string uri) {\r
162                         ArrayList headersList = new ArrayList();\r
163                         \r
164                         headersList.Add(new Header("__Uri", uri));\r
165                         headersList.Add(new Header("__MethodName", soapMessage.MethodName));\r
166                         string typeNamespace, assemblyName;\r
167                         bool b = SoapServices.DecodeXmlNamespaceForClrTypeNamespace(soapMessage.XmlNameSpace, out typeNamespace, out assemblyName);\r
168 \r
169                         _serverType = RemotingServices.GetServerTypeForUri(uri);\r
170                                 \r
171                         headersList.Add(new Header("__TypeName", _serverType.FullName, false));\r
172                         _xmlNamespace = soapMessage.XmlNameSpace;\r
173                         RemMessageType messageType;\r
174                         _methodCallInfo = _serverType.GetMethod(soapMessage.MethodName); \r
175 \r
176                         // the *out* parameters aren't serialized\r
177                         // have to add them here\r
178                         _methodCallParameters = _methodCallInfo.GetParameters();\r
179                         object[] args = new object[_methodCallParameters.Length];\r
180                         \r
181                         foreach(ParameterInfo paramInfo in _methodCallParameters)\r
182                         {\r
183                                 Type paramType = (paramInfo.ParameterType.IsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType);\r
184 \r
185                                 if (paramInfo.IsOut && paramInfo.ParameterType.IsByRef) {\r
186                                         args [paramInfo.Position] = GetNullValue (paramType);\r
187                                 }\r
188                                 else{\r
189                                         int index = Array.IndexOf(soapMessage.ParamNames, paramInfo.Name);\r
190                                         if(soapMessage.ParamValues[index] is IConvertible) \r
191                                                 soapMessage.ParamValues[index] = Convert.ChangeType(\r
192                                                                 soapMessage.ParamValues[index],\r
193                                                                 paramType);\r
194                                         args [paramInfo.Position] = soapMessage.ParamValues[index];\r
195                                 }\r
196                         }\r
197                         \r
198                         headersList.Add(new Header("__Args", args, false));\r
199                         Header[] headers = (Header[])headersList.ToArray(typeof(Header));\r
200 \r
201                         // build the MethodCall from the headers\r
202                         MethodCall mthCall = new MethodCall(headers);\r
203                         return (IMessage)mthCall;\r
204                 }\r
205                 \r
206                 // used by the server\r
207                 internal object BuildSoapMessageFromMethodResponse(IMethodReturnMessage mrm, out ITransportHeaders responseHeaders)\r
208                 {\r
209                         responseHeaders = new TransportHeaders();\r
210 \r
211                         if(mrm.Exception == null) {\r
212                                 // *normal* function return\r
213                                 \r
214                                 SoapMessage soapMessage = new SoapMessage();\r
215                                 \r
216                                 // fill the transport headers\r
217                                 responseHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";\r
218 \r
219                                 // build the SoapMessage\r
220                                 ArrayList paramNames = new ArrayList();\r
221                                 ArrayList paramValues = new ArrayList();\r
222                                 ArrayList paramTypes = new ArrayList();\r
223                                 soapMessage.MethodName = mrm.MethodName+"Response";\r
224                                 if(mrm.ReturnValue != null && mrm.ReturnValue.GetType() != typeof(void)) {\r
225                                         paramNames.Add("return");\r
226                                         paramValues.Add(mrm.ReturnValue);\r
227                                         paramTypes.Add(mrm.ReturnValue.GetType());\r
228                                 }\r
229                                 \r
230                                 for(int i = 0; i < mrm.OutArgCount; i++){\r
231                                         paramNames.Add(mrm.GetOutArgName(i));\r
232                                         paramValues.Add(mrm.GetOutArg(i));\r
233                                         if(mrm.GetOutArg(i) != null) paramTypes.Add(mrm.GetOutArg(i).GetType());\r
234                                 }\r
235                                 soapMessage.ParamNames = (string[]) paramNames.ToArray(typeof(string));\r
236                                 soapMessage.ParamValues = (object[]) paramValues.ToArray(typeof(object));\r
237                                 soapMessage.ParamTypes = (Type[]) paramTypes.ToArray(typeof(Type));\r
238                                 soapMessage.XmlNameSpace = _xmlNamespace;\r
239                                 return soapMessage;\r
240                         }\r
241                         else {\r
242                                 // an Exception was thrown while executing the function\r
243                                 responseHeaders["__HttpStatusCode"] = "400";\r
244                                 responseHeaders["__HttpReasonPhrase"] = "Bad Request";\r
245                                 // fill the transport headers\r
246                                 responseHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";\r
247                                 ServerFault serverFault = CreateServerFault(mrm.Exception);\r
248                                 return new SoapFault("Server", String.Format(" **** {0} - {1}", mrm.Exception.GetType().ToString(), mrm.Exception.Message), null, serverFault);\r
249                         }\r
250                 }\r
251                 \r
252                 internal SoapMessage CreateSoapMessage (bool isRequest)\r
253                 {\r
254                         if (isRequest) return new SoapMessage ();\r
255                         \r
256                         int n = 0;\r
257                         Type[] types = new Type [_methodCallParameters.Length + 1];\r
258                         \r
259                         if (_methodCallInfo.ReturnType != typeof(void)) {\r
260                                 types[0] = _methodCallInfo.ReturnType;\r
261                                 n++;\r
262                         }\r
263                                 \r
264                         foreach(ParameterInfo paramInfo in _methodCallParameters)\r
265                         {\r
266                                 if (paramInfo.ParameterType.IsByRef || paramInfo.IsOut)\r
267                                 {\r
268                                         Type t = paramInfo.ParameterType;\r
269                                         if (t.IsByRef) t = t.GetElementType();\r
270                                         types [n++] = t;\r
271                                 }\r
272                         }\r
273                         SoapMessage sm = new SoapMessage ();\r
274                         sm.ParamTypes = types;\r
275                         return sm;\r
276                 }\r
277                 \r
278                 // used by the server when an exception is thrown\r
279                 // by the called function\r
280                 internal ServerFault CreateServerFault(Exception e) {\r
281                         // it's really strange here\r
282                         // a ServerFault object has a private System.Exception member called *exception*\r
283                         // (have a look at a MS Soap message when an exception occurs on the server)\r
284                         // but there is not public .ctor with an Exception as parameter...????....\r
285                         // (maybe an internal one). So I searched another way...\r
286                         ServerFault sf = (ServerFault) FormatterServices.GetUninitializedObject(typeof(ServerFault));\r
287                         MemberInfo[] mi = FormatterServices.GetSerializableMembers(typeof(ServerFault), new StreamingContext(StreamingContextStates.All));\r
288                         \r
289                         FieldInfo fi;\r
290                         object[] mv = new object[mi.Length];\r
291                         for(int i = 0; i < mi.Length; i++) {\r
292                                 fi = mi[i] as FieldInfo;\r
293                                 if(fi != null && fi.FieldType == typeof(Exception)) mv[i] = e;\r
294                         }\r
295                         sf = (ServerFault) FormatterServices.PopulateObjectMembers(sf, mi, mv);\r
296                         \r
297                         return sf;\r
298                 }\r
299 \r
300                 internal void GetInfoFromMethodCallMessage(IMethodCallMessage mcm) {\r
301                         _serverType = Type.GetType(mcm.TypeName, true);\r
302                         \r
303                         if (mcm.MethodSignature != null) \r
304                                 _methodCallInfo = _serverType.GetMethod(mcm.MethodName, \r
305                                                                                                                 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, \r
306                                                                                                                 null, (Type []) mcm.MethodSignature, null);\r
307                         else\r
308                                 _methodCallInfo = _serverType.GetMethod(mcm.MethodName, \r
309                                                                                                                 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);\r
310 \r
311                         _methodCallParameters = _methodCallInfo.GetParameters();\r
312                 }       \r
313         \r
314                 object GetNullValue (Type paramType)\r
315                 {\r
316                         switch (Type.GetTypeCode (paramType))\r
317                         {\r
318                                 case TypeCode.Boolean: return false;\r
319                                 case TypeCode.Byte: return (byte)0;\r
320                                 case TypeCode.Char: return '\0';\r
321                                 case TypeCode.Decimal: return (decimal)0;\r
322                                 case TypeCode.Double: return (double)0;\r
323                                 case TypeCode.Int16: return (short)0;\r
324                                 case TypeCode.Int32: return (int)0;\r
325                                 case TypeCode.Int64: return (long)0;\r
326                                 case TypeCode.SByte: return (sbyte)0;\r
327                                 case TypeCode.Single: return (float)0;\r
328                                 case TypeCode.UInt16: return (ushort)0;\r
329                                 case TypeCode.UInt32: return (uint)0;\r
330                                 case TypeCode.UInt64: return (ulong)0;\r
331                                 default: return null;\r
332                         }\r
333                 }\r
334         }\r
335 }