// created on 03/04/2003 at 14:09
//
// System.Runtime.Remoting.Channels.SoapMessageFormatter
//
// Author: Jean-Marc Andre (jean-marc.andre@polymtl.ca)
//
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
namespace System.Runtime.Remoting.Channels{
enum RemMessageType {
MethodCall, MethodResponse, ServerFault, NotRecognize
}
internal class SoapMessageFormatter {
private static FieldInfo _serverFaultExceptionField;
private Type _serverType;
private MethodInfo _methodCallInfo;
private ParameterInfo[] _methodCallParameters;
private string _xmlNamespace;
static SoapMessageFormatter() {
// Get the ServerFault exception field FieldInfo that
// will be used later if an exception occurs on the server
MemberInfo[] mi = FormatterServices.GetSerializableMembers(typeof(ServerFault), new StreamingContext(StreamingContextStates.All));
FieldInfo fi;
for(int i = 0; i < mi.Length; i++){
fi = mi[i] as FieldInfo;
if(fi != null && fi.FieldType == typeof(Exception)){
_serverFaultExceptionField = fi;
}
}
}
internal SoapMessageFormatter() {
}
internal IMessage FormatFault (SoapFault fault, IMethodCallMessage mcm)
{
ServerFault sf = fault.Detail as ServerFault;
Exception e = null;
if (sf != null) {
if(_serverFaultExceptionField != null)
e = (Exception) _serverFaultExceptionField.GetValue(sf);
#if TARGET_JVM
if (e == null && sf.ExceptionType != null)
{
try
{
Type te = Type.GetType(sf.ExceptionType);
if (te != null)
{
ConstructorInfo ce = te.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
null, new Type[] {typeof(string)}, null);
if (ce != null)
{
e = (Exception) ce.Invoke(new object[] {sf.ExceptionMessage});
}
else
{
e = (Exception) Activator.CreateInstance(te);
}
}
}
catch
{
e = null;
}
}
#endif
}
if (e == null)
e = new RemotingException (fault.FaultString);
return new ReturnMessage((System.Exception)e, mcm);
}
// used by the client
internal IMessage FormatResponse(ISoapMessage soapMsg, IMethodCallMessage mcm)
{
IMessage rtnMsg;
if(soapMsg.MethodName == "Fault") {
// an exception was thrown by the server
Exception e = new SerializationException();
int i = Array.IndexOf(soapMsg.ParamNames, "detail");
if(_serverFaultExceptionField != null)
// todo: revue this 'cause it's not safe
e = (Exception) _serverFaultExceptionField.GetValue(
soapMsg.ParamValues[i]);
rtnMsg = new ReturnMessage((System.Exception)e, mcm);
}
else {
object rtnObject = null;
//RemMessageType messageType;
// Get the output of the function if it is not *void*
if(_methodCallInfo.ReturnType != typeof(void)){
int index = Array.IndexOf(soapMsg.ParamNames, "return");
rtnObject = soapMsg.ParamValues[index];
if(rtnObject is IConvertible)
rtnObject = Convert.ChangeType(
rtnObject,
_methodCallInfo.ReturnType);
}
object[] outParams = new object [_methodCallParameters.Length];
int n=0;
// check if there are *out* parameters
foreach(ParameterInfo paramInfo in _methodCallParameters) {
if(paramInfo.ParameterType.IsByRef || paramInfo.IsOut) {
int index = Array.IndexOf(soapMsg.ParamNames, paramInfo.Name);
object outParam = soapMsg.ParamValues[index];
if(outParam is IConvertible)
outParam = Convert.ChangeType (outParam, paramInfo.ParameterType.GetElementType());
outParams[n] = outParam;
}
else
outParams [n] = null;
n++;
}
Header[] headers = new Header [2 + (soapMsg.Headers != null ? soapMsg.Headers.Length : 0)];
headers [0] = new Header ("__Return", rtnObject);
headers [1] = new Header ("__OutArgs", outParams);
if (soapMsg.Headers != null)
soapMsg.Headers.CopyTo (headers, 2);
rtnMsg = new MethodResponse (headers, mcm);
}
return rtnMsg;
}
// used by the client
internal SoapMessage BuildSoapMessageFromMethodCall(
IMethodCallMessage mcm,
out ITransportHeaders requestHeaders)
{
requestHeaders = new TransportHeaders();
SoapMessage soapMsg = new SoapMessage();
GetInfoFromMethodCallMessage(mcm);
// Format the SoapMessage that will be used to create the RPC
soapMsg.MethodName = mcm.MethodName;
//int count = mcm.ArgCount;
ArrayList paramNames = new ArrayList(_methodCallParameters.Length);
ArrayList paramTypes = new ArrayList(_methodCallParameters.Length);
ArrayList paramValues = new ArrayList(_methodCallParameters.Length);
// Add the function parameters to the SoapMessage class
foreach(ParameterInfo paramInfo in _methodCallParameters) {
if (!(paramInfo.IsOut && paramInfo.ParameterType.IsByRef)) {
Type t = paramInfo.ParameterType;
if (t.IsByRef) t = t.GetElementType ();
paramNames.Add(paramInfo.Name);
paramTypes.Add(t);
paramValues.Add(mcm.Args[paramInfo.Position]);
}
}
soapMsg.ParamNames = (string[]) paramNames.ToArray(typeof(string));
soapMsg.ParamTypes = (Type[]) paramTypes.ToArray(typeof(Type));
soapMsg.ParamValues = (object[]) paramValues.ToArray(typeof(object));
soapMsg.XmlNameSpace = SoapServices.GetXmlNamespaceForMethodCall(_methodCallInfo);
soapMsg.Headers = BuildMessageHeaders (mcm);
// Format the transport headers
requestHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";
requestHeaders["SOAPAction"] = "\""+
SoapServices.GetSoapActionFromMethodBase(_methodCallInfo)+"\"";
requestHeaders[CommonTransportKeys.RequestUri] = mcm.Uri;
return soapMsg;
}
// used by the server
internal IMessage BuildMethodCallFromSoapMessage(SoapMessage soapMessage, string uri)
{
ArrayList headersList = new ArrayList();
Type[] signature = null;
headersList.Add(new Header("__Uri", uri));
headersList.Add(new Header("__MethodName", soapMessage.MethodName));
string typeNamespace, assemblyName;
if (!SoapServices.DecodeXmlNamespaceForClrTypeNamespace(soapMessage.XmlNameSpace, out typeNamespace, out assemblyName))
throw new RemotingException ("Could not decode SoapMessage");
// Note that we don't need to validate the type in
// this place because MethodCall will do it anyway.
if (assemblyName == null) // corlib
_serverType = Type.GetType (typeNamespace, true);
else
_serverType = Type.GetType (typeNamespace + ", " + assemblyName, true);
headersList.Add(new Header("__TypeName", _serverType.FullName, false));
if (soapMessage.Headers != null) {
foreach (Header h in soapMessage.Headers) {
headersList.Add (h);
if (h.Name == "__MethodSignature")
signature = (Type[]) h.Value;
}
}
_xmlNamespace = soapMessage.XmlNameSpace;
//RemMessageType messageType;
BindingFlags bflags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
if (signature == null)
_methodCallInfo = _serverType.GetMethod(soapMessage.MethodName, bflags);
else
_methodCallInfo = _serverType.GetMethod(soapMessage.MethodName, bflags, null, signature, null);
if (_methodCallInfo == null && (soapMessage.MethodName == "FieldSetter" || soapMessage.MethodName == "FieldGetter"))
_methodCallInfo = typeof(object).GetMethod (soapMessage.MethodName, bflags);
// the *out* parameters aren't serialized
// have to add them here
_methodCallParameters = _methodCallInfo.GetParameters();
object[] args = new object[_methodCallParameters.Length];
int sn = 0;
for (int n=0; n<_methodCallParameters.Length; n++)
{
ParameterInfo paramInfo = _methodCallParameters [n];
Type paramType = (paramInfo.ParameterType.IsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType);
if (paramInfo.IsOut && paramInfo.ParameterType.IsByRef) {
args [n] = GetNullValue (paramType);
}
else{
object val = soapMessage.ParamValues[sn++];
if(val is IConvertible)
args [n] = Convert.ChangeType (val, paramType);
else
args [n] = val;
}
}
headersList.Add(new Header("__Args", args, false));
Header[] headers = (Header[])headersList.ToArray(typeof(Header));
// build the MethodCall from the headers
MethodCall mthCall = new MethodCall(headers);
return (IMessage)mthCall;
}
// used by the server
internal object BuildSoapMessageFromMethodResponse(IMethodReturnMessage mrm, out ITransportHeaders responseHeaders)
{
responseHeaders = new TransportHeaders();
if(mrm.Exception == null) {
// *normal* function return
SoapMessage soapMessage = new SoapMessage();
// fill the transport headers
responseHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";
// build the SoapMessage
ArrayList paramNames = new ArrayList();
ArrayList paramValues = new ArrayList();
ArrayList paramTypes = new ArrayList();
soapMessage.MethodName = mrm.MethodName+"Response";
Type retType = ((MethodInfo)mrm.MethodBase).ReturnType;
if(retType != typeof(void)) {
paramNames.Add("return");
paramValues.Add(mrm.ReturnValue);
if (mrm.ReturnValue != null)
paramTypes.Add(mrm.ReturnValue.GetType());
else
paramTypes.Add(retType);
}
for(int i = 0; i < mrm.OutArgCount; i++){
paramNames.Add(mrm.GetOutArgName(i));
paramValues.Add(mrm.GetOutArg(i));
if(mrm.GetOutArg(i) != null) paramTypes.Add(mrm.GetOutArg(i).GetType());
}
soapMessage.ParamNames = (string[]) paramNames.ToArray(typeof(string));
soapMessage.ParamValues = (object[]) paramValues.ToArray(typeof(object));
soapMessage.ParamTypes = (Type[]) paramTypes.ToArray(typeof(Type));
soapMessage.XmlNameSpace = _xmlNamespace;
soapMessage.Headers = BuildMessageHeaders (mrm);
return soapMessage;
}
else {
// an Exception was thrown while executing the function
responseHeaders["__HttpStatusCode"] = "500";
responseHeaders["__HttpReasonPhrase"] = "Bad Request";
// fill the transport headers
responseHeaders["Content-Type"] = "text/xml; charset=\"utf-8\"";
ServerFault serverFault = CreateServerFault(mrm.Exception);
return new SoapFault("Server", String.Format(" **** {0} - {1}", mrm.Exception.GetType().ToString(), mrm.Exception.Message), null, serverFault);
}
}
internal SoapMessage CreateSoapMessage (bool isRequest)
{
if (isRequest) return new SoapMessage ();
int n = 0;
Type[] types = new Type [_methodCallParameters.Length + 1];
if (_methodCallInfo.ReturnType != typeof(void)) {
types[0] = _methodCallInfo.ReturnType;
n++;
}
foreach(ParameterInfo paramInfo in _methodCallParameters)
{
if (paramInfo.ParameterType.IsByRef || paramInfo.IsOut)
{
Type t = paramInfo.ParameterType;
if (t.IsByRef) t = t.GetElementType();
types [n++] = t;
}
}
SoapMessage sm = new SoapMessage ();
sm.ParamTypes = types;
return sm;
}
// used by the server when an exception is thrown
// by the called function
internal ServerFault CreateServerFault(Exception e) {
// it's really strange here
// a ServerFault object has a private System.Exception member called *exception*
// (have a look at a MS Soap message when an exception occurs on the server)
// but there is not public .ctor with an Exception as parameter...????....
// (maybe an internal one). So I searched another way...
ServerFault sf = (ServerFault) FormatterServices.GetUninitializedObject(typeof(ServerFault));
MemberInfo[] mi = FormatterServices.GetSerializableMembers(typeof(ServerFault), new StreamingContext(StreamingContextStates.All));
FieldInfo fi;
object[] mv = new object[mi.Length];
for(int i = 0; i < mi.Length; i++) {
fi = mi[i] as FieldInfo;
if(fi != null && fi.FieldType == typeof(Exception)) mv[i] = e;
}
sf = (ServerFault) FormatterServices.PopulateObjectMembers(sf, mi, mv);
return sf;
}
internal void GetInfoFromMethodCallMessage (IMethodMessage mcm) {
_serverType = Type.GetType(mcm.TypeName, true);
_methodCallInfo = RemotingServices.GetMethodBaseFromMethodMessage (mcm) as MethodInfo;
_methodCallParameters = _methodCallInfo.GetParameters();
}
Header[] BuildMessageHeaders (IMethodMessage msg)
{
ArrayList headers = new ArrayList (1);
foreach (string key in msg.Properties.Keys)
{
switch (key) {
case "__Uri":
case "__MethodName":
case "__TypeName":
case "__Args":
case "__OutArgs":
case "__Return":
case "__MethodSignature":
case "__CallContext":
continue;
default:
object value = msg.Properties [key];
if (value != null)
headers.Add (new Header (key, value, false, "http://schemas.microsoft.com/clr/soap/messageProperties"));
break;
}
}
if (RemotingServices.IsMethodOverloaded (msg))
headers.Add (new Header ("__MethodSignature", msg.MethodSignature, false, "http://schemas.microsoft.com/clr/soap/messageProperties"));
if (msg.LogicalCallContext != null && msg.LogicalCallContext.HasInfo)
headers.Add (new Header ("__CallContext", msg.LogicalCallContext, false, "http://schemas.microsoft.com/clr/soap/messageProperties"));
if (headers.Count == 0) return null;
return (Header[]) headers.ToArray (typeof(Header));
}
object GetNullValue (Type paramType)
{
#if TARGET_JVM
if (paramType.IsEnum)
{
return Activator.CreateInstance(paramType);
}
#endif
switch (Type.GetTypeCode (paramType))
{
case TypeCode.Boolean: return false;
case TypeCode.Byte: return (byte)0;
case TypeCode.Char: return '\0';
case TypeCode.Decimal: return (decimal)0;
case TypeCode.Double: return (double)0;
case TypeCode.Int16: return (short)0;
case TypeCode.Int32: return (int)0;
case TypeCode.Int64: return (long)0;
case TypeCode.SByte: return (sbyte)0;
case TypeCode.Single: return (float)0;
case TypeCode.UInt16: return (ushort)0;
case TypeCode.UInt32: return (uint)0;
case TypeCode.UInt64: return (ulong)0;
default:
#if TARGET_JVM
if (paramType.IsValueType)
{
return Activator.CreateInstance(paramType);
}
#endif
return null;
}
}
}
}
|