/*
 * The OpenSAML License, Version 1.
 * Copyright (c) 2002
 * University Corporation for Advanced Internet Development, Inc.
 * All rights reserved
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution, if any, must include
 * the following acknowledgment: "This product includes software developed by
 * the University Corporation for Advanced Internet Development
 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
 * may appear in the software itself, if and wherever such third-party
 * acknowledgments normally appear.
 *
 * Neither the name of OpenSAML nor the names of its contributors, nor
 * Internet2, nor the University Corporation for Advanced Internet Development,
 * Inc., nor UCAID may be used to endorse or promote products derived from this
 * software without specific prior written permission. For written permission,
 * please contact opensaml@opensaml.org
 *
 * Products derived from this software may not be called OpenSAML, Internet2,
 * UCAID, or the University Corporation for Advanced Internet Development, nor
 * may OpenSAML appear in their name, without prior written permission of the
 * University Corporation for Advanced Internet Development.
 *
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/* SAMLException.cpp - SAML exception class

   Scott Cantor
   2/28/02

   $History:$
*/

#include "internal.h"

using namespace saml;
using namespace std;

saml::QName SAMLException::SUCCESS(XML::SAMLP_NS,L(Success));
saml::QName SAMLException::REQUESTER(XML::SAMLP_NS,L(Requester));
saml::QName SAMLException::RESPONDER(XML::SAMLP_NS,L(Responder));
saml::QName SAMLException::VERSIONMISMATCH(XML::SAMLP_NS,L(VersionMismatch));

SAMLException::SAMLExceptionFactoryMap SAMLException::m_map;

void SAMLException::regFactory(const char* exceptionClass, SAMLExceptionFactory* factory)
{
    if (exceptionClass && factory)
        m_map.insert(SAMLExceptionFactoryMap::value_type(exceptionClass,factory));
}

void SAMLException::unregFactory(const char* exceptionClass)
{
    if (exceptionClass)
        m_map.erase(exceptionClass);
}

SAMLException* SAMLException::getInstance(DOMElement* e)
{
    // Find the StatusDetail element.
    DOMNode* detail=e->getLastChild();
    while (detail && (detail->getNodeType()!=DOMNode::ELEMENT_NODE ||
                        XMLString::compareString(XML::SAMLP_NS,detail->getNamespaceURI()) ||
                        XMLString::compareString(L(StatusDetail),detail->getLocalName())))
        detail=detail->getPreviousSibling();

    if (detail)
    {
        // Look for the special OpenSAML ExceptionClass element.
        DOMNode* eclass=detail->getFirstChild();
        while (eclass && (eclass->getNodeType()!=DOMNode::ELEMENT_NODE ||
                            XMLString::compareString(XML::OPENSAML_NS,eclass->getNamespaceURI()) ||
                            XMLString::compareString(L(ExceptionClass),eclass->getLocalName())))
            eclass=eclass->getNextSibling();
        if (eclass && eclass->getFirstChild() && eclass->getFirstChild()->getNodeType()==DOMNode::TEXT_NODE)
        {
            auto_ptr<char> name(XMLString::transcode(eclass->getFirstChild()->getNodeValue()));
            if (name.get())
            {
                SAMLExceptionFactoryMap::const_iterator i=m_map.find(name.get());
                if (i!=m_map.end())
                    return (i->second)(e);
            }
        }
    }
    
    // Default to base class.
    return new SAMLException(e);
}

SAMLException* SAMLException::getInstance(istream& in)
{
    XML::Parser p;
    XML::StreamInputSource src(in);
    Wrapper4InputSource dsrc(&src,false);
    DOMDocument* doc=p.parse(dsrc);
    try
    {
        SAMLException* e=getInstance(doc->getDocumentElement());
        if (e)
            e->setDocument(doc);
        return e;
    }
    catch(...)
    {
        doc->release();
        throw;
    }
}

SAMLException::SAMLException(const Iterator<saml::QName>& codes, const string& msg, DOMElement* detail)
    : m_typename("SAMLException"), m_msg(msg), m_detail(detail)
{
    RTTI(SAMLException);
    while (codes.hasNext())
        m_codes.push_back(codes.next());
}

SAMLException::SAMLException(const Iterator<saml::QName>& codes, const char* msg, DOMElement* detail)
    : m_typename("SAMLException"), m_detail(detail)
{
    RTTI(SAMLException);
    while (codes.hasNext())
        m_codes.push_back(codes.next());
    if (msg)
        m_msg=msg;
}

SAMLException::SAMLException(const saml::QName& code, const string& msg, DOMElement* detail)
    : m_typename("SAMLException"), m_msg(msg), m_detail(detail)
{
    RTTI(SAMLException);
    m_codes.push_back(code);
}

SAMLException::SAMLException(const saml::QName& code, const char* msg, DOMElement* detail)
    : m_typename("SAMLException"), m_detail(detail)
{
    RTTI(SAMLException);
    m_codes.push_back(code);
    if (msg)
        m_msg=msg;
}

SAMLException::SAMLException(DOMElement* e) : m_typename("SAMLException"), m_detail(NULL)
{
    RTTI(SAMLException);
    fromDOM(e);
}

SAMLException::SAMLException(istream& in) : SAMLObject(in), m_typename("SAMLException"), m_detail(NULL)
{
    RTTI(SAMLException);
    fromDOM(m_document->getDocumentElement());
}

SAMLObject* SAMLException::clone() const
{
    SAMLException* e=new SAMLException(*this);
    DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(NULL);
    e->m_detail=static_cast<DOMElement*>(impl->createDocument()->importNode(e->m_detail,true));
    return e;
}

SAMLException::~SAMLException() throw()
{
}

void SAMLException::fromDOM(DOMElement* e)
{
    SAMLObject::fromDOM(e);

    if (SAMLConfig::getConfig().strict_dom_checking &&
        (XMLString::compareString(XML::SAMLP_NS,e->getNamespaceURI()) || XMLString::compareString(L(Status),e->getLocalName())))
        throw MalformedException(SAMLException::RESPONDER,"SAMLException::fromDOM() requires samlp:Status at root");

    // Get the first child, the StatusCode.
    DOMNode* code=e->getFirstChild();
    while (code && code->getNodeType()!=DOMNode::ELEMENT_NODE)
        code=code->getNextSibling();

    // Extract the status message.
    const XMLCh* msg=NULL;
    DOMNode* m=code->getNextSibling();
    while (m && m->getNodeType()!=DOMNode::ELEMENT_NODE)
        m=m->getNextSibling();
    if (m && !XMLString::compareString(XML::SAMLP_NS,m->getNamespaceURI()) &&
        !XMLString::compareString(L(StatusMessage),m->getLocalName()))
        msg=m->getFirstChild()->getNodeValue();
    auto_ptr<char> trans(msg ? XMLString::transcode(msg) : NULL);
    if (trans.get())
        m_msg=trans.get();

    DOMNodeList* nlist=e->getElementsByTagNameNS(XML::SAMLP_NS,L(StatusCode));
    for (int i=0; nlist && i<nlist->getLength(); i++)
    {
        saml::QName* qptr=saml::QName::getQNameAttribute(static_cast<DOMElement*>(nlist->item(i)),NULL,L(Value));
        if (qptr)
            m_codes.push_back(*qptr);
        else
            throw MalformedException(SAMLException::RESPONDER,"SAMLException::fromDOM() unable to evaluate QName Value");
        delete qptr;
    }

    DOMNode* detail=e->getLastChild();
    while (detail && (detail->getNodeType()!=DOMNode::ELEMENT_NODE ||
                        XMLString::compareString(XML::SAMLP_NS,detail->getNamespaceURI()) ||
                        XMLString::compareString(L(StatusDetail),detail->getLocalName())))
        detail=detail->getPreviousSibling();
    m_detail=static_cast<DOMElement*>(detail);
}

DOMNode* SAMLException::toDOM(DOMDocument* doc, bool xmlns) const
{
    SAMLObject::toDOM(doc,xmlns);
    if (m_root)
    {
        if (xmlns)
            static_cast<DOMElement*>(m_root)->setAttributeNS(XML::XMLNS_NS,L(xmlns),XML::SAMLP_NS);
        return m_root;
    }
    if (!doc)
        doc=m_document;

    // Construct a Status element.
    DOMElement* s=doc->createElementNS(XML::SAMLP_NS,L(Status));
    if (xmlns)
        s->setAttributeNS(XML::XMLNS_NS,L(xmlns),XML::SAMLP_NS);
    s->setAttributeNS(XML::XMLNS_NS,L_QNAME(xmlns,samlp),XML::SAMLP_NS);

    if (m_codes.empty())
    {
        DOMElement* sc=doc->createElementNS(XML::SAMLP_NS,L(StatusCode));
        sc->setAttributeNS(NULL,L(Value),L(Responder));
        s->appendChild(sc);
    }
    else
    {
        DOMNode* base=s;
        for (vector<saml::QName>::const_iterator qcode=m_codes.begin(); qcode!=m_codes.end(); qcode++)
        {
            DOMElement* sc=doc->createElementNS(XML::SAMLP_NS,L(StatusCode));
            const XMLCh* codens=qcode->getNamespaceURI();
            if (XMLString::compareString(codens,XML::SAMLP_NS))
            {
                static const XMLCh nsdecl[]=
                    {chLatin_x, chLatin_m, chLatin_l, chLatin_n, chLatin_s, chColon, chLatin_c, chLatin_o, chLatin_d, chLatin_e, chNull};
                sc->setAttributeNS(XML::XMLNS_NS,nsdecl,codens);
                static const XMLCh nspre[]=
                    {chLatin_c, chLatin_o, chLatin_d, chLatin_e, chColon, chNull};
                codens=nspre;
            }
            else
            {
                static const XMLCh samlpre[]={chLatin_s, chLatin_a, chLatin_m, chLatin_l, chLatin_p, chColon, chNull};
                codens=samlpre;
            }
            xstring qval(codens);
            qval+=qcode->getLocalName();
            sc->setAttributeNS(NULL,L(Value),qval.c_str());
            base=base->appendChild(sc);
        }
    }

    if (!m_msg.empty())
    {
        DOMElement* msg=doc->createElementNS(XML::SAMLP_NS,L(StatusMessage));
        auto_ptr<XMLCh> xmsg(XMLString::transcode(m_msg.c_str()));
        msg->appendChild(doc->createTextNode(xmsg.get()));
        s->appendChild(msg);
    }
    
    if (m_detail && m_detail->getOwnerDocument()!=doc)
    {
        DOMElement* copy=static_cast<DOMElement*>(doc->importNode(m_detail,true));
        m_detail->getOwnerDocument()->release();
        m_detail=copy;
    }

    if (typeid(this)!=typeid(SAMLException))
    {
        if (!m_detail)
            m_detail=doc->createElementNS(XML::SAMLP_NS,L(StatusDetail));

        DOMNodeList* existing=m_detail->getElementsByTagNameNS(XML::OPENSAML_NS,L(ExceptionClass));
        if (!existing || existing->getLength()==0)
        {
            string temp=string("org.opensaml.") + m_typename;
            auto_ptr<XMLCh> type(XMLString::transcode(temp.c_str()));
            DOMElement* eclass=doc->createElementNS(XML::OPENSAML_NS,L(ExceptionClass));
            eclass->setAttributeNS(XML::XMLNS_NS,L(xmlns),XML::OPENSAML_NS);
            eclass->appendChild(doc->createTextNode(type.get()));
            m_detail->appendChild(eclass);
        }
    }
    else
        SAML_log.debug("toDOM() skipping type name generation for generic exception type");

    if (m_detail)
        s->appendChild(m_detail);
    return m_root=s;
}
