* ExtensionManager.cs: Create all serializers for soap extensions at once.
[mono.git] / mcs / class / System.Web.Services / System.Web.Services.Description / ProtocolImporter.cs
1 // 
2 // System.Web.Services.Description.ProtocolImporter.cs
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //   Lluis Sanchez Gual (lluis@ximian.com)
7 //
8 // Copyright (C) Tim Coleman, 2002
9 //
10
11 using System;
12 using System.CodeDom;
13 using System.Web.Services;
14 using System.Web.Services.Protocols;
15 using System.Xml.Serialization;
16 using System.Xml;
17 using System.Xml.Schema;
18 using System.Collections;
19 using System.Configuration;
20
21 namespace System.Web.Services.Description {
22         public abstract class ProtocolImporter {
23
24                 #region Fields
25
26                 Binding binding;
27                 string className;
28                 CodeIdentifiers classNames;
29                 CodeNamespace codeNamespace;
30                 CodeCompileUnit codeCompileUnit;
31                 CodeTypeDeclaration codeTypeDeclaration;
32                 Message inputMessage;
33                 string methodName;
34                 Operation operation;
35                 OperationBinding operationBinding;
36                 Message outputMessage;          
37                 Port port;
38                 PortType portType;
39                 string protocolName;
40                 Service service;
41                 ServiceDescriptionImportWarnings warnings = (ServiceDescriptionImportWarnings)0;        
42                 ServiceDescriptionImporter descriptionImporter;
43                 ImportInfo iinfo;
44                 XmlSchemas xmlSchemas;
45                 XmlSchemas soapSchemas;
46
47                 #endregion // Fields
48
49                 #region Constructors
50         
51                 protected ProtocolImporter ()
52                 {
53                 }
54                 
55                 #endregion // Constructors
56
57                 #region Properties
58
59                 [MonoTODO]
60                 public XmlSchemas AbstractSchemas {
61                         get { return descriptionImporter.Schemas; }
62                 }
63
64                 public Binding Binding {
65                         get { return binding; }
66                 }
67
68                 public string ClassName {
69                         get { return className; }
70                 }
71
72                 public CodeIdentifiers ClassNames {
73                         get { return classNames; }
74                 }
75
76                 public CodeNamespace CodeNamespace {
77                         get { return codeNamespace; }
78                 }
79
80                 public CodeTypeDeclaration CodeTypeDeclaration {
81                         get { return codeTypeDeclaration; }
82                 }
83
84                 [MonoTODO]
85                 public XmlSchemas ConcreteSchemas {
86                         get { return descriptionImporter.Schemas; }
87                 }
88
89                 public Message InputMessage {
90                         get { return inputMessage; }
91                 }
92
93                 public string MethodName {
94                         get { return methodName; }
95                 }
96
97                 public Operation Operation {
98                         get { return operation; }
99                 }
100
101                 public OperationBinding OperationBinding {
102                         get { return operationBinding; }
103                 }
104
105                 public Message OutputMessage {
106                         get { return outputMessage; }
107                 }
108
109                 public Port Port {
110                         get { return port; }
111                 }
112
113                 public PortType PortType {
114                         get { return portType; }
115                 }
116
117                 public abstract string ProtocolName {
118                         get; 
119                 }
120
121                 public XmlSchemas Schemas {
122                         get { return descriptionImporter.Schemas; }
123                 }
124
125                 public Service Service {
126                         get { return service; } 
127                 }
128
129                 public ServiceDescriptionCollection ServiceDescriptions {
130                         get { return descriptionImporter.ServiceDescriptions; }
131                 }
132
133                 public ServiceDescriptionImportStyle Style {
134                         get { return descriptionImporter.Style; }
135                 }
136
137                 public ServiceDescriptionImportWarnings Warnings {
138                         get { return warnings; }
139                         set { warnings = value; }
140                 }
141                 
142                 internal ImportInfo ImportInfo
143                 {
144                         get { return iinfo; }
145                 }
146                 
147                 internal XmlSchemas LiteralSchemas
148                 {
149                         get { return xmlSchemas; }
150                 }
151                 
152                 internal XmlSchemas EncodedSchemas
153                 {
154                         get { return soapSchemas; }
155                 }
156
157                 #endregion // Properties
158
159                 #region Methods
160                 
161                 internal bool Import (ServiceDescriptionImporter descriptionImporter, CodeNamespace codeNamespace, CodeCompileUnit codeCompileUnit, ArrayList importInfo)
162                 {
163                         this.descriptionImporter = descriptionImporter;
164                         this.classNames = new CodeIdentifiers();;
165                         this.codeNamespace = codeNamespace;
166                         this.codeCompileUnit = codeCompileUnit;
167                         
168                         warnings = (ServiceDescriptionImportWarnings) 0;
169                         
170                         bool found = false;
171                         
172                         ClasifySchemas (importInfo);
173
174                         BeginNamespace ();
175                         
176                         foreach (ImportInfo info in importInfo)
177                         {
178                                 foreach (Service service in info.ServiceDescription.Services)
179                                 {
180                                         this.service = service;
181                                         int bindingCount = 0;
182                                         foreach (Port port in service.Ports)
183                                         {
184                                                 binding = ServiceDescriptions.GetBinding (port.Binding);
185                                                 if (IsBindingSupported ()) bindingCount ++;
186                                         }
187                                         
188                                         foreach (Port port in service.Ports)
189                                         {
190                                                 this.iinfo = info;
191                                                 this.port = port;
192                                                 binding = ServiceDescriptions.GetBinding (port.Binding);
193                                                 if (!IsBindingSupported ()) continue;
194                                                 
195                                                 found = true;
196                                                 ImportPortBinding (bindingCount > 1);
197                                         }
198                                 }
199                         }
200
201                         EndNamespace ();
202                         
203                         return true;
204                 }
205
206                 void ImportPortBinding (bool multipleBindings)
207                 {
208                         if (multipleBindings) className = port.Name;
209                         else className = service.Name;
210                         
211                         className = classNames.AddUnique (CodeIdentifier.MakeValid (className), port);
212                         
213                         try
214                         {
215                                 portType = ServiceDescriptions.GetPortType (binding.Type);
216                                 if (portType == null) throw new Exception ("Port type not found: " + binding.Type);
217
218                                 CodeTypeDeclaration codeClass = BeginClass ();
219                                 codeTypeDeclaration = codeClass;
220                                 AddCodeType (codeClass, port.Documentation);
221                                 codeClass.Attributes = MemberAttributes.Public;
222                                 
223                                 if (service.Documentation != null && service.Documentation != "")
224                                         AddComments (codeClass, service.Documentation);
225
226                                 CodeAttributeDeclaration att = new CodeAttributeDeclaration ("System.Diagnostics.DebuggerStepThroughAttribute");
227                                 AddCustomAttribute (codeClass, att, true);
228
229                                 att = new CodeAttributeDeclaration ("System.ComponentModel.DesignerCategoryAttribute");
230                                 att.Arguments.Add (GetArg ("code"));
231                                 AddCustomAttribute (codeClass, att, true);
232
233                                 if (binding.Operations.Count == 0) {
234                                         warnings |= ServiceDescriptionImportWarnings.NoMethodsGenerated;
235                                         return;
236                                 }
237                                 
238                                 foreach (OperationBinding oper in binding.Operations) 
239                                 {
240                                         operationBinding = oper;
241                                         operation = FindPortOperation ();
242                                         if (operation == null) throw new Exception ("Operation " + operationBinding.Name + " not found in portType " + PortType.Name);
243
244                                         foreach (OperationMessage omsg in operation.Messages)
245                                         {
246                                                 Message msg = ServiceDescriptions.GetMessage (omsg.Message);
247                                                 if (msg == null) throw new Exception ("Message not found: " + omsg.Message);
248                                                 
249                                                 if (omsg is OperationInput)
250                                                         inputMessage = msg;
251                                                 else
252                                                         outputMessage = msg;
253                                         }
254                                         
255                                         CodeMemberMethod method = GenerateMethod ();
256                                         
257                                         if (method != null)
258                                         {
259                                                 methodName = method.Name;
260                                                 if (operation.Documentation != null && operation.Documentation != "")
261                                                         AddComments (method, operation.Documentation);
262                                         }
263                                 }
264                                 
265                                 EndClass ();
266                         }
267                         catch (InvalidOperationException ex)
268                         {
269                                 warnings |= ServiceDescriptionImportWarnings.NoCodeGenerated;
270                                 UnsupportedBindingWarning (ex.Message);
271                         }
272                 }
273
274                 Operation FindPortOperation ()
275                 {
276                         string inMessage = null;
277                         string outMessage = null;
278                         int numMsg = 1;
279                         
280                         if (operationBinding.Input == null) throw new InvalidOperationException ("Input operation binding not found");
281                         inMessage = (operationBinding.Input.Name != null) ? operationBinding.Input.Name : operationBinding.Name;
282                                 
283                         if (operationBinding.Output != null) {
284                                 outMessage = (operationBinding.Output.Name != null) ? operationBinding.Output.Name : operationBinding.Name;
285                                 numMsg++;
286                         }
287                         
288                         string operName = operationBinding.Name;
289                         
290                         Operation foundOper = null;
291                         foreach (Operation oper in PortType.Operations)
292                         {
293                                 if (oper.Name == operName)
294                                 {
295                                         int hits = 0;
296                                         foreach (OperationMessage omsg in oper.Messages)
297                                         {
298                                                 if (omsg is OperationInput && GetOperMessageName (omsg, operName) == inMessage) hits++;
299                                                 if (omsg is OperationOutput && GetOperMessageName (omsg, operName) == outMessage) hits++;
300                                         }
301                                         if (hits == numMsg) return oper;
302                                         foundOper = oper;
303                                 }
304                         }
305                         return foundOper;
306                 }
307                 
308                 string GetOperMessageName (OperationMessage msg, string operName)
309                 {
310                         if (msg.Name == null) return operName;
311                         else return msg.Name;
312                 }
313                 
314                 internal string GetServiceUrl (string location)
315                 {
316                         if (ImportInfo.AppSettingUrlKey == null || ImportInfo.AppSettingUrlKey == string.Empty)
317                                 return location;
318                         else
319                         {
320                                 string url;
321                                 if (Style == ServiceDescriptionImportStyle.Server) throw new InvalidOperationException ("Cannot set appSettingUrlKey if Style is Server");
322                                 url = ConfigurationSettings.AppSettings [ImportInfo.AppSettingUrlKey];
323                                 if (ImportInfo.AppSettingBaseUrl != null && ImportInfo.AppSettingBaseUrl != string.Empty)
324                                         url += "/" + ImportInfo.AppSettingBaseUrl + "/" + location;
325                                 return url;
326                         }
327                 }
328
329                 void ClasifySchemas (ArrayList importInfo)
330                 {
331                         // I don't like this, but I could not find any other way of clasifying
332                         // schemas between encoded and literal.
333                         
334                         xmlSchemas = new XmlSchemas ();
335                         soapSchemas = new XmlSchemas ();
336                         
337                         foreach (ImportInfo info in importInfo)
338                         {
339                                 foreach (Service service in info.ServiceDescription.Services)
340                                 {
341                                         foreach (Port port in service.Ports)
342                                         {
343                                                 this.iinfo = info;
344                                                 this.port = port;
345                                                 binding = ServiceDescriptions.GetBinding (port.Binding);
346                                                 if (binding == null) continue;
347                                                 portType = ServiceDescriptions.GetPortType (binding.Type);
348                                                 if (portType == null) continue;
349                                                 
350                                                 foreach (OperationBinding oper in binding.Operations) 
351                                                 {
352                                                         operationBinding = oper;
353                                                         operation = FindPortOperation ();
354                                                         if (operation == null) continue;
355                 
356                                                         foreach (OperationMessage omsg in operation.Messages)
357                                                         {
358                                                                 Message msg = ServiceDescriptions.GetMessage (omsg.Message);
359                                                                 if (msg == null) continue;
360                                                                 
361                                                                 if (omsg is OperationInput)
362                                                                         inputMessage = msg;
363                                                                 else
364                                                                         outputMessage = msg;
365                                                         }
366                                                         
367                                                         if (GetMessageEncoding (oper.Input) == SoapBindingUse.Encoded)
368                                                                 AddMessageSchema (soapSchemas, oper.Input, inputMessage);
369                                                         else
370                                                                 AddMessageSchema (xmlSchemas, oper.Input, inputMessage);
371                                                         
372                                                         if (oper.Output != null) {
373                                                                 if (GetMessageEncoding (oper.Output) == SoapBindingUse.Encoded)
374                                                                         AddMessageSchema (soapSchemas, oper.Output, outputMessage);
375                                                                 else
376                                                                         AddMessageSchema (xmlSchemas, oper.Output, outputMessage);
377                                                         }
378                                                 }
379                                         }
380                                 }
381                         }
382                         
383                         XmlSchemas defaultList = xmlSchemas;
384                                 
385                         if (xmlSchemas.Count == 0 && soapSchemas.Count > 0)
386                                 defaultList = soapSchemas;
387                                 
388                         // Schemas not referenced by any message
389                         foreach (XmlSchema sc in Schemas)
390                         {
391                                 if (ImportsEncodedNamespace (sc))
392                                         soapSchemas.Add (sc);
393                                 else if (!soapSchemas.Contains (sc) && !xmlSchemas.Contains (sc))
394                                         defaultList.Add (sc);
395                         }
396                 }
397                         
398                 void AddMessageSchema (XmlSchemas schemas, MessageBinding mb, Message msg)
399                 {
400                         foreach (MessagePart part in msg.Parts)
401                         {
402                                 if (part.Element != XmlQualifiedName.Empty)
403                                         AddIncludingSchema (schemas, part.Element.Namespace);
404                                 else if (part.Type != XmlQualifiedName.Empty)
405                                         AddIncludingSchema (schemas, part.Type.Namespace);
406                         }
407                         SoapBodyBinding sbb = mb.Extensions.Find (typeof(SoapBodyBinding)) as SoapBodyBinding;
408                         if (sbb != null) AddIncludingSchema (schemas, sbb.Namespace);
409                 }
410                 
411                 void AddIncludingSchema (XmlSchemas list, string ns)
412                 {
413                         XmlSchema sc = Schemas [ns];
414                         if (sc == null || list.Contains (sc)) return;
415                         list.Add (sc);
416                         foreach (XmlSchemaObject ob in sc.Includes)
417                         {
418                                 XmlSchemaImport import = ob as XmlSchemaImport;
419                                 if (import != null) AddIncludingSchema (list, import.Namespace);
420                         }
421                 }
422                 
423                 SoapBindingUse GetMessageEncoding (MessageBinding mb)
424                 {
425                         SoapBodyBinding sbb = mb.Extensions.Find (typeof(SoapBodyBinding)) as SoapBodyBinding;
426                         if (sbb == null)
427                         {
428                                 if (mb is InputBinding) return SoapBindingUse.Encoded;
429                                 else return SoapBindingUse.Literal;
430                         }
431                         else 
432                                 if (sbb.Use == SoapBindingUse.Encoded) return SoapBindingUse.Encoded;
433                         else
434                                 return SoapBindingUse.Literal;
435                 }
436                 
437                 bool ImportsEncodedNamespace (XmlSchema sc)
438                 {
439                         foreach (XmlSchemaObject ob in sc.Includes)
440                         {
441                                 XmlSchemaImport import = ob as XmlSchemaImport;
442                                 if (import.Namespace == SoapProtocolReflector.EncodingNamespace) return true;
443                         }
444                         return false;
445                 }
446                 
447                 [MonoTODO]
448                 public void AddExtensionWarningComments (CodeCommentStatementCollection comments, ServiceDescriptionFormatExtensionCollection extensions) 
449                 {
450                         throw new NotImplementedException ();
451                 }
452
453                 protected abstract CodeTypeDeclaration BeginClass ();
454
455                 protected virtual void BeginNamespace ()
456                 {
457                 }
458
459                 protected virtual void EndClass ()
460                 {
461                 }
462
463                 protected virtual void EndNamespace ()
464                 {
465                 }
466
467                 protected abstract CodeMemberMethod GenerateMethod ();
468                 protected abstract bool IsBindingSupported ();
469                 protected abstract bool IsOperationFlowSupported (OperationFlow flow);
470                 
471                 [MonoTODO]
472                 public Exception OperationBindingSyntaxException (string text)
473                 {
474                         throw new NotImplementedException ();
475                 }
476
477                 [MonoTODO]
478                 public Exception OperationSyntaxException (string text)
479                 {
480                         throw new NotImplementedException ();
481                 }
482
483                 public void UnsupportedBindingWarning (string text)
484                 {
485                         warnings |= ServiceDescriptionImportWarnings.UnsupportedBindingsIgnored;
486                         AddGlobalComments ("WARNING: Could not generate proxy for binding " + binding.Name + ". " + text);
487                 }
488
489                 public void UnsupportedOperationBindingWarning (string text)
490                 {
491                         warnings |= ServiceDescriptionImportWarnings.UnsupportedOperationsIgnored;
492                         AddGlobalComments ("WARNING: Could not generate operation " + OperationBinding.Name + ". " + text);
493                 }
494
495                 public void UnsupportedOperationWarning (string text)
496                 {
497                         warnings |= ServiceDescriptionImportWarnings.UnsupportedOperationsIgnored;
498                         AddGlobalComments ("WARNING: Could not generate operation " + OperationBinding.Name + ". " + text);
499                 }
500
501                 void AddGlobalComments (string comments)
502                 {
503                         codeNamespace.Comments.Add (new CodeCommentStatement (comments, false));
504                 }
505
506                 void AddComments (CodeTypeMember member, string comments)
507                 {
508                         if (comments == null || comments == "") member.Comments.Add (new CodeCommentStatement ("<remarks/>", true));
509                         else member.Comments.Add (new CodeCommentStatement ("<remarks>\n" + comments + "\n</remarks>", true));
510                 }
511
512                 void AddCodeType (CodeTypeDeclaration type, string comments)
513                 {
514                         AddComments (type, comments);
515                         codeNamespace.Types.Add (type);
516                 }
517
518                 internal void AddCustomAttribute (CodeTypeMember ctm, CodeAttributeDeclaration att, bool addIfNoParams)
519                 {
520                         if (att.Arguments.Count == 0 && !addIfNoParams) return;
521                         
522                         if (ctm.CustomAttributes == null) ctm.CustomAttributes = new CodeAttributeDeclarationCollection ();
523                         ctm.CustomAttributes.Add (att);
524                 }
525
526                 internal void AddCustomAttribute (CodeTypeMember ctm, string name, params CodeAttributeArgument[] args)
527                 {
528                         if (ctm.CustomAttributes == null) ctm.CustomAttributes = new CodeAttributeDeclarationCollection ();
529                         ctm.CustomAttributes.Add (new CodeAttributeDeclaration (name, args));
530                 }
531
532                 internal CodeAttributeArgument GetArg (string name, object value)
533                 {
534                         return new CodeAttributeArgument (name, new CodePrimitiveExpression(value));
535                 }
536
537                 internal CodeAttributeArgument GetEnumArg (string name, string enumType, string enumValue)
538                 {
539                         return new CodeAttributeArgument (name, new CodeFieldReferenceExpression (new CodeTypeReferenceExpression(enumType), enumValue));
540                 }
541
542                 internal CodeAttributeArgument GetArg (object value)
543                 {
544                         return new CodeAttributeArgument (new CodePrimitiveExpression(value));
545                 }
546
547                 internal CodeAttributeArgument GetTypeArg (string name, string typeName)
548                 {
549                         return new CodeAttributeArgument (name, new CodeTypeOfExpression(typeName));
550                 }
551                 
552                 #endregion
553         }
554 }