1 //------------------------------------------------------------------------------
2 // <copyright file="DiscoveryClientProtocol.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.Services.Discovery {
9 using System.Xml.Serialization;
12 using System.Web.Services;
13 using System.Web.Services.Protocols;
15 using System.Collections;
16 using System.Diagnostics;
17 using System.Web.Services.Configuration;
19 using System.Security.Permissions;
20 using System.Globalization;
21 using System.Threading;
22 using System.Runtime.InteropServices;
23 using System.Web.Services.Diagnostics;
25 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol"]/*' />
27 /// <para>[To be supplied.]</para>
29 public class DiscoveryClientProtocol : HttpWebClientProtocol {
30 private DiscoveryClientReferenceCollection references = new DiscoveryClientReferenceCollection();
31 private DiscoveryClientDocumentCollection documents = new DiscoveryClientDocumentCollection();
32 private Hashtable inlinedSchemas = new Hashtable();
33 private ArrayList additionalInformation = new ArrayList();
34 private DiscoveryExceptionDictionary errors = new DiscoveryExceptionDictionary();
36 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.DiscoveryClientProtocol"]/*' />
38 /// <para>[To be supplied.]</para>
40 public DiscoveryClientProtocol()
44 internal DiscoveryClientProtocol(HttpWebClientProtocol protocol) : base(protocol) {
47 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.AdditionalInformation"]/*' />
49 /// <para>[To be supplied.]</para>
51 public IList AdditionalInformation {
53 return additionalInformation;
57 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.Documents"]/*' />
59 /// <para>[To be supplied.]</para>
61 public DiscoveryClientDocumentCollection Documents {
67 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.Errors"]/*' />
69 /// <para>[To be supplied.]</para>
71 public DiscoveryExceptionDictionary Errors {
77 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.References"]/*' />
79 /// <para>[To be supplied.]</para>
81 public DiscoveryClientReferenceCollection References {
87 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.References"]/*' />
89 /// <para>[To be supplied.]</para>
91 internal Hashtable InlinedSchemas
95 return inlinedSchemas;
98 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.Discover"]/*' />
100 /// <para>[To be supplied.]</para>
102 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
103 public DiscoveryDocument Discover(string url) {
104 DiscoveryDocument doc = Documents[url] as DiscoveryDocument;
108 DiscoveryDocumentReference docRef = new DiscoveryDocumentReference(url);
109 docRef.ClientProtocol = this;
110 References[url] = docRef;
113 // this will auto-resolve and place the document in the Documents hashtable.
114 return docRef.Document;
117 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.DiscoverAny"]/*' />
119 /// <para>[To be supplied.]</para>
121 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
122 public DiscoveryDocument DiscoverAny(string url) {
123 Type[] refTypes = WebServicesSection.Current.DiscoveryReferenceTypes;
124 DiscoveryReference discoRef = null;
125 string contentType = null;
126 Stream stream = Download(ref url, ref contentType);
129 bool allErrorsAreHtmlContentType = true;
130 Exception errorInValidDocument = null;
131 ArrayList specialErrorMessages = new ArrayList();
132 foreach (Type type in refTypes) {
133 if (!typeof(DiscoveryReference).IsAssignableFrom(type))
135 discoRef = (DiscoveryReference) Activator.CreateInstance(type);
137 discoRef.ClientProtocol = this;
139 Exception e = discoRef.AttemptResolve(contentType, stream);
143 Errors[type.FullName] = e;
146 InvalidContentTypeException e2 = e as InvalidContentTypeException;
147 if (e2 == null || !ContentType.MatchesBase(e2.ContentType, "text/html"))
148 allErrorsAreHtmlContentType = false;
150 InvalidDocumentContentsException e3 = e as InvalidDocumentContentsException;
152 errorInValidDocument = e;
156 if (e.InnerException != null && e.InnerException.InnerException == null)
157 specialErrorMessages.Add(e.InnerException.Message);
160 if (discoRef == null) {
161 if (errorInValidDocument != null) {
162 StringBuilder errorMessage = new StringBuilder(Res.GetString(Res.TheDocumentWasUnderstoodButContainsErrors));
163 while (errorInValidDocument != null) {
164 errorMessage.Append("\n - ").Append(errorInValidDocument.Message);
165 errorInValidDocument = errorInValidDocument.InnerException;
167 throw new InvalidOperationException(errorMessage.ToString());
169 else if (allErrorsAreHtmlContentType) {
170 throw new InvalidOperationException(Res.GetString(Res.TheHTMLDocumentDoesNotContainDiscoveryInformation));
173 bool same = specialErrorMessages.Count == Errors.Count && Errors.Count > 0;
174 for (int i = 1; same && i < specialErrorMessages.Count; i++) {
175 if ((string) specialErrorMessages[i - 1] != (string) specialErrorMessages[i])
179 throw new InvalidOperationException(Res.GetString(Res.TheDocumentWasNotRecognizedAsAKnownDocumentType, specialErrorMessages[0]));
182 StringBuilder errorMessage = new StringBuilder(Res.GetString(Res.WebMissingResource, url));
183 foreach (DictionaryEntry entry in Errors) {
184 e = (Exception)(entry.Value);
185 string refType = (string)(entry.Key);
186 if (0 == string.Compare(refType, typeof(ContractReference).FullName, StringComparison.Ordinal)) {
187 refType = Res.GetString(Res.WebContractReferenceName);
189 else if (0 == string.Compare(refType, typeof(SchemaReference).FullName, StringComparison.Ordinal)) {
190 refType = Res.GetString(Res.WebShemaReferenceName);
192 else if (0 == string.Compare(refType, typeof(DiscoveryDocumentReference).FullName, StringComparison.Ordinal)) {
193 refType = Res.GetString(Res.WebDiscoveryDocumentReferenceName);
195 errorMessage.Append("\n- ").Append(Res.GetString(Res.WebDiscoRefReport,
198 while (e.InnerException != null) {
199 errorMessage.Append("\n - ").Append(e.InnerException.Message);
200 e = e.InnerException;
203 throw new InvalidOperationException(errorMessage.ToString());
208 if (discoRef is DiscoveryDocumentReference)
209 return ((DiscoveryDocumentReference) discoRef).Document;
211 References[discoRef.Url] = discoRef;
212 DiscoveryDocument doc = new DiscoveryDocument();
213 doc.References.Add(discoRef);
217 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.Download"]/*' />
219 /// <para>[To be supplied.]</para>
221 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
222 public Stream Download(ref string url) {
223 string contentType = null;
224 return Download(ref url, ref contentType);
227 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.Download1"]/*' />
229 /// <para>[To be supplied.]</para>
231 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
232 public Stream Download(ref string url, ref string contentType) {
233 WebRequest request = GetWebRequest(new Uri(url));
234 request.Method = "GET";
236 HttpWebRequest httpRequest = request as HttpWebRequest;
237 if (httpRequest != null) {
238 httpRequest.Timeout = httpRequest.Timeout * 2;
241 WebResponse response = null;
243 response = GetWebResponse(request);
245 catch (Exception e) {
246 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
249 throw new WebException(Res.GetString(Res.ThereWasAnErrorDownloading0, url), e);
251 HttpWebResponse httpResponse = response as HttpWebResponse;
252 if (httpResponse != null) {
253 if (httpResponse.StatusCode != HttpStatusCode.OK) {
254 string errorMessage = RequestResponseUtils.CreateResponseExceptionString(httpResponse);
255 throw new WebException(Res.GetString(Res.ThereWasAnErrorDownloading0, url), new WebException(errorMessage, null, WebExceptionStatus.ProtocolError, response));
258 Stream responseStream = response.GetResponseStream();
260 // Uri.ToString() returns the unescaped version
261 url = response.ResponseUri.ToString();
262 contentType = response.ContentType;
264 if (response.ResponseUri.Scheme == Uri.UriSchemeFtp ||
265 response.ResponseUri.Scheme == Uri.UriSchemeFile) {
266 int dotIndex = response.ResponseUri.AbsolutePath.LastIndexOf('.');
267 if (dotIndex != -1) {
268 switch (response.ResponseUri.AbsolutePath.Substring(dotIndex + 1).ToLower(CultureInfo.InvariantCulture)) {
273 contentType = ContentType.TextXml;
281 // need to return a buffered stream (one that supports CanSeek)
282 return RequestResponseUtils.StreamToMemoryStream(responseStream);
285 responseStream.Close();
289 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.LoadExternals"]/*' />
291 /// <para>[To be supplied.]</para>
294 [Obsolete("This method will be removed from a future version. The method call is no longer required for resource discovery", false)]
296 public void LoadExternals() { }
298 internal void FixupReferences() {
299 foreach (DiscoveryReference reference in References.Values) {
300 reference.LoadExternals(InlinedSchemas);
302 foreach (string url in InlinedSchemas.Keys) {
303 Documents.Remove(url);
307 private static bool IsFilenameInUse(Hashtable filenames, string path) {
308 return filenames[path.ToLower(CultureInfo.InvariantCulture)] != null;
311 private static void AddFilename(Hashtable filenames, string path) {
312 filenames.Add(path.ToLower(CultureInfo.InvariantCulture), path);
315 private static string GetUniqueFilename(Hashtable filenames, string path) {
316 if (IsFilenameInUse(filenames, path)) {
317 string extension = Path.GetExtension(path);
318 string allElse = path.Substring(0, path.Length - extension.Length);
321 path = allElse + append.ToString(CultureInfo.InvariantCulture) + extension;
323 } while (IsFilenameInUse(filenames, path));
326 AddFilename(filenames, path);
330 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.ReadAll"]/*' />
332 /// <para>[To be supplied.]</para>
334 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
335 public DiscoveryClientResultCollection ReadAll(string topLevelFilename) {
336 XmlSerializer ser = new XmlSerializer(typeof(DiscoveryClientResultsFile));
337 Stream file = File.OpenRead(topLevelFilename);
338 string topLevelPath = Path.GetDirectoryName(topLevelFilename);
339 DiscoveryClientResultsFile results = null;
341 results = (DiscoveryClientResultsFile) ser.Deserialize(file);
342 for (int i = 0; i < results.Results.Count; i++) {
343 if (results.Results[i] == null)
344 throw new InvalidOperationException(Res.GetString(Res.WebNullRef));
345 string typeName = results.Results[i].ReferenceTypeName;
346 if (typeName == null || typeName.Length == 0)
347 throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute, "referenceType"));
348 DiscoveryReference reference = (DiscoveryReference) Activator.CreateInstance(Type.GetType(typeName));
349 reference.ClientProtocol = this;
351 string url = results.Results[i].Url;
352 if (url == null || url.Length == 0)
353 throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute2, reference.GetType().FullName, "url"));
355 string fileName = results.Results[i].Filename;
356 if (fileName == null || fileName.Length == 0)
357 throw new InvalidOperationException(Res.GetString(Res.WebRefInvalidAttribute2, reference.GetType().FullName, "filename"));
359 Stream docFile = File.OpenRead(Path.Combine(topLevelPath, results.Results[i].Filename));
361 Documents[reference.Url] = reference.ReadDocument(docFile);
362 Debug.Assert(Documents[reference.Url] != null, "Couldn't deserialize file " + results.Results[i].Filename);
367 References[reference.Url] = reference;
374 return results.Results;
377 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.ResolveAll"]/*' />
379 /// <para>[To be supplied.]</para>
381 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
382 public void ResolveAll() {
383 // Resolve until we reach a 'steady state' (no more references added)
385 int resolvedCount = InlinedSchemas.Keys.Count;
386 while (resolvedCount != References.Count) {
387 resolvedCount = References.Count;
388 DiscoveryReference[] refs = new DiscoveryReference[References.Count];
389 References.Values.CopyTo(refs, 0);
390 for (int i = 0; i < refs.Length; i++) {
391 DiscoveryReference discoRef = refs[i];
392 if (discoRef is DiscoveryDocumentReference) {
394 // Resolve discovery document references deeply
395 ((DiscoveryDocumentReference)discoRef).ResolveAll(true);
397 catch (Exception e) {
398 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
401 // don't let the exception out - keep going. Just add it to the list of errors.
402 Errors[discoRef.Url] = e;
403 if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveAll", e);
410 catch (Exception e) {
411 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
414 // don't let the exception out - keep going. Just add it to the list of errors.
415 Errors[discoRef.Url] = e;
416 if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveAll", e);
424 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.ResolveOneLevel"]/*' />
426 /// <para>[To be supplied.]</para>
428 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
429 public void ResolveOneLevel() {
430 // download everything we have a reference to, but don't recurse.
432 DiscoveryReference[] refs = new DiscoveryReference[References.Count];
433 References.Values.CopyTo(refs, 0);
434 for (int i = 0; i < refs.Length; i++) {
438 catch (Exception e) {
439 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
442 // don't let the exception out - keep going. Just add it to the list of errors.
443 Errors[refs[i].Url] = e;
444 if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveOneLevel", e);
449 private static string GetRelativePath(string fullPath, string relativeTo) {
450 string currentDir = Path.GetDirectoryName(Path.GetFullPath(relativeTo));
453 while (currentDir.Length > 0) {
454 if (currentDir.Length <= fullPath.Length && string.Compare(currentDir, fullPath.Substring(0, currentDir.Length), StringComparison.OrdinalIgnoreCase) == 0) {
455 answer += fullPath.Substring(currentDir.Length);
456 if (answer.StartsWith("\\", StringComparison.Ordinal))
457 answer = answer.Substring(1);
461 if (currentDir.Length < 2)
464 int lastSlash = currentDir.LastIndexOf('\\', currentDir.Length - 2);
465 currentDir = currentDir.Substring(0, lastSlash + 1);
471 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.WriteAll"]/*' />
473 /// <para>[To be supplied.]</para>
475 [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
476 public DiscoveryClientResultCollection WriteAll(string directory, string topLevelFilename) {
477 DiscoveryClientResultsFile results = new DiscoveryClientResultsFile();
478 Hashtable filenames = new Hashtable();
479 string topLevelFullPath = Path.Combine(directory, topLevelFilename);
481 // write out each of the documents
482 DictionaryEntry[] entries = new DictionaryEntry[Documents.Count + InlinedSchemas.Keys.Count];
484 foreach (DictionaryEntry entry in Documents) {
485 entries[i++] = entry;
487 foreach (DictionaryEntry entry in InlinedSchemas) {
488 entries[i++] = entry;
490 foreach (DictionaryEntry entry in entries) {
491 string url = (string) entry.Key;
492 object document = entry.Value;
493 if (document == null)
495 DiscoveryReference reference = References[url];
496 string filename = reference == null ? DiscoveryReference.FilenameFromUrl(Url) : reference.DefaultFilename;
497 filename = GetUniqueFilename(filenames, Path.GetFullPath(Path.Combine(directory, filename)));
498 results.Results.Add(new DiscoveryClientResult(reference == null ? null : reference.GetType(), url, GetRelativePath(filename, topLevelFullPath)));
499 Stream file = File.Create(filename);
501 reference.WriteDocument(document, file);
508 // write out the file that points to all those documents.
509 XmlSerializer ser = new XmlSerializer(typeof(DiscoveryClientResultsFile));
510 Stream topLevelFile = File.Create(topLevelFullPath);
512 ser.Serialize(new StreamWriter(topLevelFile, new UTF8Encoding(false)), results);
515 topLevelFile.Close();
518 return results.Results;
527 public sealed class DiscoveryClientResultsFile {
528 private DiscoveryClientResultCollection results = new DiscoveryClientResultCollection();
529 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientProtocol.DiscoveryClientResultsFile.Results"]/*' />
531 /// <para>[To be supplied.]</para>
533 public DiscoveryClientResultCollection Results {
542 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResultCollection"]/*' />
544 /// <para>[To be supplied.]</para>
546 public sealed class DiscoveryClientResultCollection : CollectionBase {
548 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResultCollection.this"]/*' />
550 /// <para>[To be supplied.]</para>
552 public DiscoveryClientResult this[int i] {
554 return (DiscoveryClientResult) List[i];
561 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResultCollection.Add"]/*' />
563 /// <para>[To be supplied.]</para>
565 public int Add(DiscoveryClientResult value) {
566 return List.Add(value);
569 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResultCollection.Contains"]/*' />
571 /// <para>[To be supplied.]</para>
573 public bool Contains(DiscoveryClientResult value) {
574 return List.Contains(value);
577 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResultCollection.Remove"]/*' />
579 /// <para>[To be supplied.]</para>
581 public void Remove(DiscoveryClientResult value) {
587 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult"]/*' />
589 /// <para>[To be supplied.]</para>
591 public sealed class DiscoveryClientResult {
592 string referenceTypeName;
596 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult.DiscoveryClientResult"]/*' />
598 /// <para>[To be supplied.]</para>
600 public DiscoveryClientResult() {
603 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult.DiscoveryClientResult1"]/*' />
605 /// <para>[To be supplied.]</para>
607 public DiscoveryClientResult(Type referenceType, string url, string filename) {
608 this.referenceTypeName = referenceType == null ? string.Empty : referenceType.FullName;
610 this.filename = filename;
613 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult.ReferenceTypeName"]/*' />
615 /// <para>[To be supplied.]</para>
617 [XmlAttribute("referenceType")]
618 public string ReferenceTypeName {
620 return referenceTypeName;
623 referenceTypeName = value;
627 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult.Url"]/*' />
629 /// <para>[To be supplied.]</para>
631 [XmlAttribute("url")]
641 /// <include file='doc\DiscoveryClientProtocol.uex' path='docs/doc[@for="DiscoveryClientResult.Filename"]/*' />
643 /// <para>[To be supplied.]</para>
645 [XmlAttribute("filename")]
646 public string Filename {