File: System\Web\Services\Discovery\DiscoveryDocumentReference.cs
Project: ndp\cdf\src\NetFx20\System.Web.Services\System.Web.Services.csproj (System.Web.Services)
//------------------------------------------------------------------------------
// <copyright file="DiscoveryDocumentReference.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Web.Services.Discovery {
 
    using System;
    using System.Net;
    using System.Xml;
    using System.Diagnostics;
    using System.IO;
    using System.Xml.Serialization;
    using System.Web.Services.Protocols;
    using System.Web.Services.Configuration;
    using System.Text;
    using System.Globalization;
    using System.Threading;
    using System.Collections;
    using System.Web.Services.Diagnostics;
 
    /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference"]/*' />
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    [XmlRoot("discoveryRef", Namespace = DiscoveryDocument.Namespace)]
    public sealed class DiscoveryDocumentReference : DiscoveryReference {
 
        private string reference;
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.DiscoveryDocumentReference"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public DiscoveryDocumentReference() {
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.DiscoveryDocumentReference1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public DiscoveryDocumentReference(string href) {
            Ref = href;
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.Ref"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlAttribute("ref")]
        public string Ref {
            get {
                return reference == null ? "" : reference;
            }
            set {
                reference = value;
            }
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.DefaultFilename"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlIgnore]
        public override string DefaultFilename {
            get {
                string filename = FilenameFromUrl(Url);
                return Path.ChangeExtension(filename, ".disco");        // [Microsoft] change default extension
            }
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.Document"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlIgnore]
        public DiscoveryDocument Document {
            get {
                if (ClientProtocol == null)
                    throw new InvalidOperationException(Res.GetString(Res.WebMissingClientProtocol));
                object document = ClientProtocol.Documents[Url];
                if (document == null) {
                    Resolve();
                    document = ClientProtocol.Documents[Url];
                }
                DiscoveryDocument discoDocument = document as DiscoveryDocument;
                if (discoDocument == null) {
                    throw new InvalidOperationException(Res.GetString(Res.WebInvalidDocType,
                                                      typeof(DiscoveryDocument).FullName,
                                                      document == null ? string.Empty : document.GetType().FullName,
                                                      Url));
                }
                return discoDocument;
            }
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.WriteDocument"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override void WriteDocument(object document, Stream stream) {
            WebServicesSection.Current.DiscoveryDocumentSerializer.Serialize(new StreamWriter(stream, new UTF8Encoding(false)), document);
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.ReadDocument"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override object ReadDocument(Stream stream) {
            return WebServicesSection.Current.DiscoveryDocumentSerializer.Deserialize(stream);
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.Url"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [XmlIgnore]
        public override string Url {
            get { return Ref; }
            set { Ref = value; }
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.GetDocumentNoParse"]/*' />
        /// <devdoc>
        /// Retrieves a discovery document from Url, either out of the ClientProtocol
        /// or from a stream. Does not
        /// </devdoc>
        private static DiscoveryDocument GetDocumentNoParse(ref string url, DiscoveryClientProtocol client) {
            DiscoveryDocument d = (DiscoveryDocument) client.Documents[url];
            if (d != null) {
                return d;
            }
 
            string contentType = null;
 
            Stream stream = client.Download(ref url, ref contentType);
            try {
                XmlTextReader reader = new XmlTextReader(new StreamReader(stream, RequestResponseUtils.GetEncoding(contentType)));
                reader.WhitespaceHandling = WhitespaceHandling.Significant;
                reader.XmlResolver = null;
                reader.DtdProcessing = DtdProcessing.Prohibit;
                if (!DiscoveryDocument.CanRead(reader)) {
                    // there is no discovery document at this location
                    ArgumentException exception = new ArgumentException(Res.GetString(Res.WebInvalidFormat));
                    throw new InvalidOperationException(Res.GetString(Res.WebMissingDocument, url), exception);
                }
                return DiscoveryDocument.Read(reader);
            }
            finally {
                stream.Close();
            }
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.Resolve"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        protected internal override void Resolve(string contentType, Stream stream) {
            DiscoveryDocument document = null;
 
            if (ContentType.IsHtml(contentType)) {
                string newRef = LinkGrep.SearchForLink(stream);
                if (newRef != null) {
                    string newUrl = UriToString(Url, newRef);
                    document = GetDocumentNoParse(ref newUrl, ClientProtocol);
                    Url = newUrl;
                }
                else
                    throw new InvalidContentTypeException(Res.GetString(Res.WebInvalidContentType, contentType), contentType);
            }
 
            if (document == null) { // probably xml...
                XmlTextReader reader = new XmlTextReader(new StreamReader(stream, RequestResponseUtils.GetEncoding(contentType)));
                reader.XmlResolver = null;
                reader.WhitespaceHandling = WhitespaceHandling.Significant;
                reader.DtdProcessing = DtdProcessing.Prohibit;
                if (DiscoveryDocument.CanRead(reader)) {
                    // it's a discovery document, so just read it.
                    document = DiscoveryDocument.Read(reader);
                }
                else {
                    // check out the processing instructions before the first tag.  if any of them
                    // match the form specified in the DISCO spec, save the href.
                    stream.Position = 0;
                    XmlTextReader newReader = new XmlTextReader(new StreamReader(stream, RequestResponseUtils.GetEncoding(contentType)));
                    newReader.XmlResolver = null;
                    newReader.DtdProcessing = DtdProcessing.Prohibit;
                    while (newReader.NodeType != XmlNodeType.Element) {
                        if (newReader.NodeType == XmlNodeType.ProcessingInstruction) {
                            // manually parse the PI contents since XmlTextReader won't automatically do it
                            StringBuilder sb = new StringBuilder("<pi ");
                            sb.Append(newReader.Value);
                            sb.Append("/>");
                            XmlTextReader piReader = new XmlTextReader(new StringReader(sb.ToString()));
                            piReader.XmlResolver = null;
                            piReader.DtdProcessing = DtdProcessing.Prohibit;
                            piReader.Read();
                            string type = piReader["type"];
                            string alternate = piReader["alternate"];
                            string href = piReader["href"];
                            if (type != null && ContentType.MatchesBase(type, ContentType.TextXml)
                                && alternate != null && string.Compare(alternate, "yes", StringComparison.OrdinalIgnoreCase) == 0
                                && href != null) {
                                // we got a PI with the right attributes
 
                                // there is a link to a discovery document. follow it after fully qualifying it.
                                string newUrl = UriToString(Url, href);
                                document = GetDocumentNoParse(ref newUrl, ClientProtocol);
                                Url = newUrl;
                                break;
                            }
                        }
                        newReader.Read();
                    }
                }
            }
 
            if (document == null) {
                // there is no discovery document at this location
                Exception exception;
                if (ContentType.IsXml(contentType)) {
                    exception = new ArgumentException(Res.GetString(Res.WebInvalidFormat));
                }
                else {
                    exception = new InvalidContentTypeException(Res.GetString(Res.WebInvalidContentType, contentType), contentType);
                }
                throw new InvalidOperationException(Res.GetString(Res.WebMissingDocument, Url), exception);
            }
 
            ClientProtocol.References[Url] = this;
            ClientProtocol.Documents[Url] = document;
 
            foreach (object o in document.References) {
                if (o is DiscoveryReference) {
                    DiscoveryReference r = (DiscoveryReference) o;
                    if (r.Url.Length == 0) {
                        throw new InvalidOperationException(Res.GetString(Res.WebEmptyRef, r.GetType().FullName, Url));
                    }
                    r.Url = UriToString(Url, r.Url);
                    //All inheritors of DiscoveryReference that got URIs relative
                    //to Ref property should adjust them like ContractReference does here.
                    ContractReference cr = r as ContractReference;
                    if ( (cr != null) && (cr.DocRef != null) ) {
                        cr.DocRef = UriToString(Url, cr.DocRef);
                    }
                    r.ClientProtocol = ClientProtocol;
                    ClientProtocol.References[r.Url] = r;
                }
                else
                    ClientProtocol.AdditionalInformation.Add(o);
            }
 
            return;
        }
 
        /// <include file='doc\DiscoveryDocumentReference.uex' path='docs/doc[@for="DiscoveryDocumentReference.ResolveAll"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void ResolveAll() {
            ResolveAll(true);
        }
 
        internal void ResolveAll(bool throwOnError) {
            try {
                Resolve();
            }
            catch (Exception e) {
                if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                    throw;
                }
                if (throwOnError)
                    throw;
 
                if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "ResolveAll", e);
 
                // can't continue, because we couldn't find a document.
                return;
            }
 
            foreach (object o in Document.References) {
                DiscoveryDocumentReference r = o as DiscoveryDocumentReference;
                if (r == null)
                    continue;
                if (ClientProtocol.Documents[r.Url] != null) {
                    continue;
                }
                r.ClientProtocol = ClientProtocol;
                r.ResolveAll(throwOnError);
            }
        }
    }
}