/*  Copyright (c) 2000, 2002 by Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v1.http; 
import java.net.URL; 
import java.net.URLConnection; 
import java.net.MalformedURLException; 
import java.io.InputStream; 
import java.io.BufferedReader; 
import java.io.FileInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.ByteArrayInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.PrintWriter; 
import oracle.xml.parser.v2.DOMParser; 
import oracle.xml.parser.v2.XMLDocument; 
import oracle.xml.parser.v2.XMLDocumentFragment; 
import oracle.xml.parser.v2.XSLProcessor; 
import oracle.xml.parser.v2.XSLStylesheet; 
import oracle.xml.parser.v2.XSLException; 
import oracle.xml.parser.v2.XMLParseException; 
import oracle.portal.provider.v1.AuthenticationException; 
import oracle.portal.provider.v1.PortletException; 
import oracle.portal.provider.v1.PortletRendererUtil; 
import oracle.portal.provider.v1.ExternalPrincipal;
import oracle.portal.provider.v1.ProviderSession;
import org.w3c.dom.Node; 
import org.w3c.dom.Element; 
import org.w3c.dom.NamedNodeMap; 
import org.w3c.dom.NodeList; 
import org.w3c.dom.Document; 
import org.xml.sax.SAXException; 
import org.w3c.tidy.Tidy; 
import oracle.portal.provider.v1.Provider; 
import oracle.portal.provider.v1.ContentFilter; 
import oracle.portal.provider.v1.PortletRenderRequest; 
import oracle.portal.utils.xml.v1.DefaultNodeHandler; 

/**
 * XmlFilter {@link oracle.portal.utils.xml.v1.XmlFilter} gets
 * the contents of the URL specified in &lt;pageUrl&gt; tag in provider.xml. <br>
 * If URL content type is HTML then the filter converts HTML source into XHTML and
 * then applies default stylesheet specifed in &lt;stylesheet&gt; tag in provider.xml
 * If URL content type is XML then the filter applies the stylesheet specifed.
 *
 */
public class XmlFilter extends BaseContentFilter 
{ 
    // Tidy is used build XHTML for given HTML content
    private Tidy tidy; 
    
    //Value of <stylesheet> Tag
    private String mStylesheet = null; 
    
    //Value of <mHostName> Tag
    private String mHostName = null; 
    
    //xdoc is used construct XMLDocument
    private XMLDocument xdoc = null; 
    
    //StringBuffer is used to build XML from XHTML using Tidy
    private StringBuffer stringBuffer; 
    
    //Debug Level
    private int debugLevel = 0; 
    private InputStream stream = null; 
    
    /**
     * sets the stylesheet's URL from the provider.xml
     * @param String stylesheet URL specified
     */
    public void setStylesheet(String stylesheet) 
    { 
        mStylesheet = stylesheet; 
    } // setStylesheet()
    
    /**
     * gets the stylesheet's URL from the provider.xml
     * @return String URL location
     */
    public String getStylesheet() 
    { 
        return mStylesheet; 
    } //getStylesheet()
    
    /**
     * cloneXMLFragment is used to cloneXMLFragment containing XHTML for the passed HTML
     * @param     Node     node
     * @parm      Node     curTarget
     */
    private void cloneXMLFragment(Node node, Node curTarget) 
    { 
        if ( node == null ) return; 
        Document d = curTarget instanceof Document ? (Document)curTarget : curTarget.getOwnerDocument(); 
        int type = node.getNodeType(); 
        switch ( type ) 
        { 
            
            // If we get the root node of the document, start the recursion
            // by calling build the Doc Element
            case Node.DOCUMENT_NODE: 
            cloneXMLFragment(((Document)node).getDocumentElement(),d); 
            break; 
            
            // If we get an Element in the JTidy DOM, create Element in Oracle DOM
            // and append it to the current target node as a child. Also build
            // Oracle DOM attribute nodes for each attrib of the JTidy DOM Element
            case Node.ELEMENT_NODE: 
            Element e = d.createElement(node.getNodeName()); 
            NamedNodeMap attrs = node.getAttributes(); 
            for ( int i = 0; i < attrs.getLength(); i++) 
            { 
                e.setAttribute(attrs.item(i).getNodeName(),attrs.item(i).getNodeValue()); 
            } 
            curTarget.appendChild(e); 
            NodeList children = node.getChildNodes(); 
            
            // Recurse to build any children
            if ( children != null ) 
            { 
                int len = children.getLength(); 
                for ( int i = 0; i < len; i++) 
                { 
                    cloneXMLFragment(children.item(i),e); 
                } 
            } 
            break; 
            
            // If we get a Text Node in the JTidy DOM, create Text Node in Oracle
            // DOM and append it to the current target node as a child
            case Node.TEXT_NODE: 
            curTarget.appendChild(d.createTextNode(node.getNodeValue())); 
            break; 
        } 
    } 
    
    /**
     * return InputStream for the the given URL.
     * @param String URL loaction
     * @exception MalformedURLException
     * @exception IOException
     * @return    InputStream Returns the InputStream stream
     */
    private InputStream getURLInputStream(String loc) throws MalformedURLException,IOException 
    { 
        URL URLStream = new URL(loc); 
        URLConnection ucon = URLStream.openConnection(); 
        InputStream xStream = ucon.getInputStream(); 
        return xStream; 
    } // getURLInputStream  method
    
    /**
     * processXML method takes InputStream as arguments, it check for the stylesheet
     * protocol and applys the default style
     * @param InputStream in
     */
    private String processXML(InputStream in) throws XMLParseException , XSLException , SAXException ,IOException 
    { 
        stream = null; 
        String result = null; 
        ByteArrayOutputStream xbaos = new ByteArrayOutputStream(); 
        
        // checking for inputsource for the stylesheet
        if (mStylesheet.startsWith("http")) 
        { 
            stream = this.getURLInputStream(mStylesheet); 
        } 
        else  
        { 
            stream = (InputStream)(new FileInputStream(mStylesheet)); 
        } 
        XSLProcessor processor = new XSLProcessor(); 
        
        // Building a new DOM object
        DOMParser parser = new DOMParser(); 
        parser.setValidationMode(false); 
        parser.parse(in); 
        xdoc = parser.getDocument(); 
        XSLStylesheet xss = new XSLStylesheet(stream,null); 
        processor.processXSL(xss,xdoc,xbaos); 
        result = xbaos.toString(); 
        return result; 
    } // processXML method
    
    /**
     * filterResponse method takes PortletRenderRequest and InputStream as arguments,
     * If the "filterType" is text/html, the coming InputStream is converted into XHTML
     * using Tidy.
     * An oracle DOM is construsted using the XHTML source and a stylesheet (defaultXhtml.xsl)
     * is applied on it.
     * If the "filterType" is text/xml, the coming XML is processed by spectifed
     * stylesheet in provider.xml
     * @param PortletRenderRequest pr
     * @param java.io.InputStream urlStream
     */
    public void filterContent(PortletRenderRequest pr, InputStream urlStream) 
    throws IOException, PortletException 
    { 
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
        String resultXHTML = null; 
        String finalContent = null; 
        Provider provider = pr.getProvider(); 

        ProviderSession pSession = pr.getUser().getSession();
        
        //Read ServletConfig object from attribute list, and get the debuglevel from
        //zone.properties file. Store it in the provider's session attribute list.
        debugLevel = UrlServicesUtils.readDebugLevel(pr); 
        UrlServicesUtils.logMessage(provider, "filterResponse : XmlFilter : Entering filterResponse ...", debugLevel,UrlServicesUtils.BASIC_LOGGING); 
        UrlServicesUtils.logMessage(provider, "filterResponse : XmlFilter : UrlContent Type = " + getUrlContentType(), debugLevel); 
        UrlServicesUtils.logMessage(provider,"filterResponse : Entered try ...", debugLevel); 
        if (getUrlContentType().equalsIgnoreCase(UrlServicesUtils.HTML)) 
        { 
            UrlServicesUtils.logMessage(provider,"filterResponse : Entering if(HTML) condition ...", debugLevel); 
            tidy = new Tidy(); 
            tidy.setMakeClean(true); 
            tidy.setBreakBeforeBR(true); 
            tidy.setShowWarnings(false); 
            tidy.setOnlyErrors(false); 
            tidy.setQuoteAmpersand(true); 
            tidy.setQuoteNbsp(true); 
            tidy.setQuoteMarks(true); 
            tidy.setWrapScriptlets(true); 
            tidy.setWrapAsp(true); 
            tidy.setQuiet(true); 
            UrlServicesUtils.logMessage(provider,"filterResponse : After All set methods on tidy", debugLevel); 
            UrlServicesUtils.logMessage(provider,"filterResponse : Creating Document obj for the urlContent", debugLevel); 
            XMLDocument xmldocToReturn = new XMLDocument(); 
            
            // Ask Tidy to parse the incoming HTML into an in-memory DOM tree
            Document tidiedHTMLDoc = tidy.parseDOM(urlStream, null); 
            
            // Clone the JTidy DOM tree by recursively building up an Oracle DOM copy
            this.cloneXMLFragment(tidiedHTMLDoc,xmldocToReturn); 
            xmldocToReturn.print(baos); 
            UrlServicesUtils.logMessage(provider,"XmlFilter : before ByteArrayInputStream input" , debugLevel); 
            UrlServicesUtils.logMessage(provider,"*******XmlFilter :Before applying Style Sheet ***********\n" + baos.toString(), debugLevel,UrlServicesUtils.XHTML_CONTENTS_LOGGING); 
            ByteArrayInputStream input = new ByteArrayInputStream(baos.toString().getBytes()); 
            try  
            { 
                finalContent = this.processXML(input); 
            } 
            catch (SAXException se) 
            { 
                UrlServicesUtils.logMessage(provider,
                    "XmlFilter : Exception while processing XML file.", se, 
                    debugLevel); 
                throw new PortletException(se.fillInStackTrace()); 
            } 
            catch (XSLException xe) 
            { 
                UrlServicesUtils.logMessage(provider,
                    "XmlFilter : Stylesheet is not understood by parser.", 
                    xe,
                    debugLevel); 
                throw new PortletException(xe.fillInStackTrace()); 
            } 
            UrlServicesUtils.logMessage(provider,"*******XmlFilter :After Applying Style Sheet " + finalContent, debugLevel,UrlServicesUtils.XHTML_CONTENTS_LOGGING); 
            input.close(); 
        } 
        else if (getUrlContentType().equalsIgnoreCase(UrlServicesUtils.XML)) 
        { 
            UrlServicesUtils.logMessage(provider,"XmlFilter : Entering XML " + getUrlContentType(), debugLevel); 
            if (mStylesheet != null) 
            { 
                UrlServicesUtils.logMessage(provider,"XmlFilter : if xsl is not null ", debugLevel); 
                try  
                { 
                    finalContent = this.processXML(urlStream); 
                } 
                catch (SAXException se) 
                { 
                    UrlServicesUtils.logMessage(provider,
                        "XmlFilter : Exception while processing XML file.", se, 
                        debugLevel); 
                    throw new PortletException(se.fillInStackTrace()); 
                } 
                catch (XSLException xe) 
                { 
                    UrlServicesUtils.logMessage(provider,
                        "XmlFilter : Stylesheet is not understood by parser.", 
                        xe,
                        debugLevel); 
                    throw new PortletException(xe.fillInStackTrace()); 
                } 
            } // if condition for styleSheet
        } // else if loop

        // order of searching for base href.
        // a) provider.xml
        // b) HTML Content for <base href="..."> tag
        // c) Parsing pageUrl
            
        // a) from provider.xml
        String baseHref = getBaseHRef();

        // b) from HTML Content
        if(baseHref == null)
        {
            baseHref = popBaseHref(finalContent); 
        }
            
        // c) from pageUrl
        if(baseHref == null)
        {
            baseHref = getBaseHRef(pr);   
        }
        if ((getUseAuthLinks() != null) && (finalContent != null) ) 
        { 
            UrlServicesUtils.logMessage(pr.getProvider(), 
            "filterContent() Value check for AuthType: "+ 
            getUseAuthLinks(),debugLevel); 
            if (getUseAuthLinks().equals(UrlServicesUtils.TRUE)) 
            { 
                UrlServicesUtils.logMessage( 
                pr.getProvider(), 
                "XmlFilter : AuthType is true.",debugLevel); 
                
                //Check if inline rendering has been set to true.
                //If so we do not need to perform any kind of authentication
                //as the request again comes back to URLRenderer, which takes
                //care of authenticating.
                //Check for type of authentication
                if (getInlineRendering().equalsIgnoreCase(UrlServicesUtils.FALSE)) 
                { 
                    Authentication authentication = 
                        (Authentication) ((URLProvider)provider).getAuthentication();
                    boolean isBasicAuth = 
                        authentication.getAuthType().
                                       equalsIgnoreCase(UrlServicesUtils.BASIC_AUTHENTICATION);

                    ExternalPrincipal externalApp = null;
                    String user = null;
                    String password = null;
                    
                    if(pSession != null)
                    {
                        externalApp = (ExternalPrincipal) 
                            pSession.getAttribute(UrlServicesUtils.EXTERNAL_APP);
                        if(externalApp != null)
                        {
                            user = externalApp.getName();
                            password = externalApp.getPassword();
                        }
                    }
                    
                    boolean isValidUser = (user != null) && (password != null);
                    if (isBasicAuth && isValidUser) 
                    { 
                        StringBuffer authString = new StringBuffer(20);
                        authString.append(user).append(":").append(password);
                        authString.append("@");
                        //Then basic authentication
                        finalContent = this.authenticateBasicLoginLinks( 
                        pr,finalContent, 
                        authString.toString(), 
                        debugLevel); 
                    } 
                    else if (getRedirectUrlFieldName() != null) 
                    { 
                        
                        //Form based authentication
                        finalContent = authenticateFormLoginLinks( 
                        pr,finalContent, 
                        getRedirectUrlFieldName(), 
                        debugLevel); 
                    } 
                    else  
                    { 
                        UrlServicesUtils.logMessage( 
                        pr.getProvider(), 
                        "XmlFilter : No Redirect URL Field Name specified.", 
                        debugLevel); 
                    } 
                } //end of inline rendering = false
                else  
                { 
                    
                    //Add javascript to links ...
                    finalContent = addJavaScriptToLink(pr, 
                    new StringBuffer(finalContent), 
                    baseHref, "<a", "href", 
                    debugLevel); 
                    finalContent = addJavaScriptSubmitFunction(pr, 
                    finalContent, 
                    debugLevel); 
                } //end of if inline true.
            } 
        } // End of check for authilinks tag being null
        else if (getInlineRendering().equalsIgnoreCase(UrlServicesUtils.TRUE)) 
        { 
            
            //Add javascript to links ...
            finalContent = addJavaScriptToLink(pr, 
            new StringBuffer(finalContent), 
            baseHref, "<a", "href", 
            debugLevel); 
            finalContent = addJavaScriptSubmitFunction(pr, 
            finalContent, 
            debugLevel); 
        } //end of useAuthLinks = false && inlinerendering = true.
        
        //Render
        renderContents(
            pr,
            finalContent,
            debugLevel, 
            UrlServicesUtils.HTML_CONTENTS_LOGGING); 
            
        UrlServicesUtils.logMessage(pr.getProvider(),"XmlFilter: Leaving", 
        debugLevel,UrlServicesUtils.BASIC_LOGGING); 
    } // filterContent method
} // Class XmlFilter