efb346bdf215a5c6e33817302864be4042ae9e7c
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Description / ServiceMetadataExtension.cs
1 //
2 // ServiceMetadataExtension.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Ankit Jain <jankit@novell.com>
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //
9 // Copyright (C) 2005 Novell, Inc.  http://www.novell.com
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Collections.ObjectModel;
34 using System.Collections.Specialized;
35 using System.IO;
36 using System.ServiceModel.Channels;
37 using System.ServiceModel.Dispatcher;
38 using System.Web;
39 using System.Web.Services;
40 using System.Web.Services.Description;
41 using System.Xml;
42 using System.Xml.Schema;
43
44 using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
45 using WSMessage = System.Web.Services.Description.Message;
46 using SMMessage = System.ServiceModel.Channels.Message;
47 using WCFBinding = System.ServiceModel.Channels.Binding;
48
49 namespace System.ServiceModel.Description
50 {
51         public class ServiceMetadataExtension : IExtension<ServiceHostBase>
52         {
53                 const string ServiceMetadataBehaviorHttpGetBinding = "ServiceMetadataBehaviorHttpGetBinding";
54
55                 MetadataSet metadata;
56                 ServiceHostBase owner;
57                 Dictionary<Uri, ChannelDispatcherBase> _serviceMetadataChanelDispatchers;
58                 
59                 [MonoTODO]
60                 public ServiceMetadataExtension ()
61                 {
62                 }
63
64                 [MonoTODO]
65                 public MetadataSet Metadata {
66                         get {
67                                 if (metadata == null) {
68                                         MetadataExporter exporter = new WsdlExporter ();
69                                         foreach (ServiceEndpoint ep in owner.Description.Endpoints) {
70                                                 if (ep.Contract.Name == ServiceMetadataBehavior.MexContractName)
71                                                         continue;
72
73                                                 exporter.ExportEndpoint (ep);
74                                         }
75                                         metadata = exporter.GetGeneratedMetadata ();
76                                 }
77                                 return metadata;
78                         }
79                 }
80
81                 internal ServiceHostBase Owner {
82                         get { return owner; }
83                 }
84
85                 internal static ServiceMetadataExtension EnsureServiceMetadataExtension (ServiceDescription description, ServiceHostBase serviceHostBase) {
86                         ServiceMetadataExtension sme = serviceHostBase.Extensions.Find<ServiceMetadataExtension> ();
87                         if (sme == null) {
88                                 sme = new ServiceMetadataExtension ();
89                                 serviceHostBase.Extensions.Add (sme);
90                         }
91                         return sme;
92                 }
93
94                 internal static void EnsureServiceMetadataHttpChanelDispatcher (ServiceDescription description, ServiceHostBase serviceHostBase, ServiceMetadataExtension sme, Uri uri, WCFBinding binding)
95                 {
96                         EnsureServiceMetadataDispatcher (description, serviceHostBase, sme, uri, binding ?? MetadataExchangeBindings.CreateMexHttpBinding ());
97                 }
98
99                 internal static void EnsureServiceMetadataHttpsChanelDispatcher (ServiceDescription description, ServiceHostBase serviceHostBase, ServiceMetadataExtension sme, Uri uri, WCFBinding binding)
100                 {
101                         // same as http now.
102                         EnsureServiceMetadataDispatcher (description, serviceHostBase, sme, uri, binding ?? MetadataExchangeBindings.CreateMexHttpsBinding ());
103                 }
104
105                 static void EnsureServiceMetadataDispatcher (ServiceDescription description, ServiceHostBase serviceHostBase, ServiceMetadataExtension sme, Uri uri, WCFBinding binding)
106                 {
107                         if (sme._serviceMetadataChanelDispatchers == null)
108                                 sme._serviceMetadataChanelDispatchers = new Dictionary<Uri, ChannelDispatcherBase> ();
109                         else if (sme._serviceMetadataChanelDispatchers.ContainsKey (uri))
110                                 return;
111
112                         CustomBinding cb = new CustomBinding (binding)
113                         {
114                                 Name = ServiceMetadataBehaviorHttpGetBinding,
115                         };
116                         cb.Elements.Find<MessageEncodingBindingElement> ().MessageVersion = MessageVersion.None;
117
118                         ServiceEndpoint se = new ServiceEndpoint (ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract)), cb, new EndpointAddress (uri))
119                         {
120                                 ListenUri = uri,
121                         };
122
123                         ChannelDispatcher channelDispatcher = serviceHostBase.BuildChannelDispatcher (se, new BindingParameterCollection ());
124
125                         channelDispatcher.Endpoints [0].DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider (new InstanceContext (serviceHostBase, new HttpGetWsdl (sme, uri)));
126
127                         sme._serviceMetadataChanelDispatchers.Add (uri, channelDispatcher);
128                         serviceHostBase.ChannelDispatchers.Add (channelDispatcher);
129                 }
130
131                 void IExtension<ServiceHostBase>.Attach (ServiceHostBase owner)
132                 {
133                         this.owner = owner;
134                 }
135
136                 [MonoTODO]
137                 void IExtension<ServiceHostBase>.Detach (ServiceHostBase owner)
138                 {
139                         throw new NotImplementedException ();
140                 }
141         }
142
143         [ServiceContract (Namespace = "http://schemas.microsoft.com/2006/04/http/metadata")]
144         interface IHttpGetHelpPageAndMetadataContract
145         {
146                 [OperationContract (Action = "*", ReplyAction = "*")]
147                 SMMessage Get (SMMessage req);
148         }
149
150         class HttpGetWsdl : IHttpGetHelpPageAndMetadataContract
151         {
152                 ServiceMetadataExtension metadata_extn;
153                 Uri base_uri;
154
155                 Dictionary <string,WSServiceDescription> wsdl_documents = 
156                         new Dictionary<string, WSServiceDescription> ();
157                 Dictionary <string, XmlSchema> schemas = 
158                         new Dictionary<string, XmlSchema> ();
159
160                 public HttpGetWsdl (ServiceMetadataExtension metadata_extn, Uri base_uri)
161                 {
162                         this.metadata_extn = metadata_extn;
163                         this.base_uri = base_uri;
164                         GetMetadata (metadata_extn.Owner);
165                 }
166                 
167                 public SMMessage Get (SMMessage req)
168                 {
169                         HttpRequestMessageProperty prop = (HttpRequestMessageProperty) req.Properties [HttpRequestMessageProperty.Name];
170
171                         NameValueCollection query_string = CreateQueryString (prop.QueryString);
172                         if (query_string == null || query_string.AllKeys.Length != 1)
173                                 return CreateHelpPage (req);
174
175                         if (query_string [null] == "wsdl") {
176                                 WSServiceDescription wsdl = GetWsdl ("wsdl");
177                                 if (wsdl != null)
178                                         return CreateWsdlMessage (wsdl);
179                         } else if (query_string ["wsdl"] != null) {
180                                 WSServiceDescription wsdl = GetWsdl (query_string ["wsdl"]);
181                                 if (wsdl != null)
182                                         return CreateWsdlMessage (wsdl);
183                         } else if (query_string ["xsd"] != null) {
184                                 XmlSchema schema = GetXmlSchema (query_string ["xsd"]);
185                                 if (schema != null) {
186                                         //FIXME: Is this the correct way?
187                                         MemoryStream ms = new MemoryStream ();
188
189                                         schema.Write (ms);
190                                         ms.Seek (0, SeekOrigin.Begin);
191                                         SMMessage ret = SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
192
193                                         return ret;
194                                 }
195                         }
196
197                         return CreateHelpPage (req);
198                 }
199
200                 /* Code from HttpListenerRequest */
201                 NameValueCollection CreateQueryString (string query)
202                 {
203                         NameValueCollection query_string = new NameValueCollection ();
204                         if (query == null || query.Length == 0)
205                                 return null;
206
207                         string [] components = query.Split ('&');
208                         foreach (string kv in components) {
209                                 int pos = kv.IndexOf ('=');
210                                 if (pos == -1) {
211                                         query_string.Add (null, HttpUtility.UrlDecode (kv));
212                                 } else {
213                                         string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
214                                         string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
215
216                                         query_string.Add (key, val);
217                                 }
218                         }
219
220                         return query_string;
221                 }
222
223                 SMMessage CreateHelpPage (SMMessage request)
224                 {
225                         //FIXME Check for ServiceDebugBehavior.HttpHelpPage
226                         //else do what? Check
227                         throw new NotImplementedException ();
228                 }
229
230                 SMMessage CreateWsdlMessage (WSServiceDescription wsdl)
231                 {
232                         MemoryStream ms = new MemoryStream ();
233                         XmlWriter xw = XmlWriter.Create (ms);
234
235                         WSServiceDescription.Serializer.Serialize (xw, wsdl);
236                         ms.Seek (0, SeekOrigin.Begin);
237                         return SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
238                 }
239
240                 void GetMetadata (ServiceHostBase host)
241                 {
242                         MetadataSet metadata = metadata_extn.Metadata;
243                         int xs_i = 0, wsdl_i = 0;
244
245                         //Dictionary keyed by namespace
246                         StringDictionary wsdl_strings = new StringDictionary ();
247                         StringDictionary xsd_strings = new StringDictionary ();
248
249                         foreach (MetadataSection section in metadata.MetadataSections) {
250                                 string key;
251
252                                 XmlSchema xs = section.Metadata as XmlSchema;
253                                 if (xs != null) {
254                                         key = String.Format ("xsd{0}", xs_i ++);
255                                         schemas [key] = xs;
256                                         xsd_strings [xs.TargetNamespace] = key;
257                                         continue;
258                                 }
259
260                                 WSServiceDescription wsdl = section.Metadata as WSServiceDescription;
261                                 if (wsdl == null)
262                                         continue;
263
264                                 //if (wsdl.TargetNamespace == "http://tempuri.org/")
265                                 if (wsdl.Services.Count > 0)
266                                         key = "wsdl";
267                                 else
268                                         key = String.Format ("wsdl{0}", wsdl_i ++);
269
270                                 wsdl_documents [key] = wsdl;
271                                 wsdl_strings [wsdl.TargetNamespace] = key;
272                         }
273                         
274                         string base_url = base_uri.ToString ();
275                         foreach (WSServiceDescription wsdl in wsdl_documents.Values) {
276                                 foreach (Import import in wsdl.Imports) {
277                                         if (!String.IsNullOrEmpty (import.Location))
278                                                 continue;
279
280                                         import.Location = String.Format ("{0}?wsdl={1}", base_url, wsdl_strings [import.Namespace]);
281                                 }
282
283                                 foreach (XmlSchema schema in wsdl.Types.Schemas) {
284                                         foreach (XmlSchemaObject obj in schema.Includes) {
285                                                 XmlSchemaImport imp = obj as XmlSchemaImport;
286                                                 if (imp == null || imp.SchemaLocation != null)
287                                                         continue;
288
289                                                 imp.SchemaLocation = String.Format ("{0}?xsd={1}", base_url, xsd_strings [imp.Namespace]);
290                                         }
291                                 }
292                         }
293
294                 }
295                 
296                 WSServiceDescription GetWsdl (string which)
297                 {
298                         WSServiceDescription wsdl;
299                         wsdl_documents.TryGetValue (which, out wsdl);
300                         return wsdl;
301                 }
302                 
303                 XmlSchema GetXmlSchema (string which)
304                 {
305                         XmlSchema schema;
306                         schemas.TryGetValue (which, out schema);
307                         return schema;
308                 }
309
310         }
311
312 }