import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.ibm.xml.enc.AlgorithmFactoryExtn;
import com.ibm.xml.enc.EncryptionContext;

import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.ByteArrayInputStream;
import java.util.Vector;
import org.apache.xpath.XPathAPI;

public class WSSMessage
{
    private Document wssMessage = null;

    WSSMessage(String wssMessageString) {

        /*
         *  The incoming string is supposed to be a valid and well-formed SOAP document.
         *  If it is not a SOAP document, throw InvalidSOAPMessage exception and return.
         *  If it is a valid SOAP message, load it into a DOM document.
         *  The WSSMessage string may or may not contain
         *  a WSS security header and XMLDS / XENC tags.
         *  Parse WSS Security header and XMLDS/XENC tags to:
         *  1. Load all tokens into a list of Token objects.
         *  2. Load all the ds:Signature elements into a list of Signature objects.
         *     Each Signature object also needs a Token associated with the Signature.
         *     So detect which Token object corresponds to a Signature object
         *     and pass on the Token to the Signature constructor.
         *  3. Load all the xenc:EncryptedData elements into a list of EncryptedData objects.
         *     Each EncryptedData object also needs a Token associated with it.
         *     So detect which Token object corresponds to an Encrypteddata object
         *     and pass on the Token to the EncryptedData constructor.
         */
        DOMParser dp = new DOMParser();
        InputStream byteData = null;
        byteData = new ByteArrayInputStream (wssMessageString.getBytes());
        try{
            dp.parse ( new InputSource ( byteData ) );
        }
        catch ( Exception e ){
            System.out.println ( "Exception in parsing the document..." );
            e.printStackTrace();
        }

        wssMessage = dp.getDocument();

    }//WSSMessage


    public Document getWSSDocument() {
        return wssMessage;

    }//getWSSDocument()


    public boolean addToken(Token token) {
        /*
         *  Prepend the token to the WSS security header.
         */
        return false;
    }//addToken


    public String addId(String XPathExpression, String wsuId) {
        /*
         *  If any of the two parameters of this method is null, return null.
         *  Apply the XPathExpression XPath filter to the WSS message
         *  to come up with a single element.
         *  If the XPath expression results in more than one element, return null.
         *  Add an attribute named wsu:Id to the element.
         *  The value of the wsu:Id attribute should be wsuId.
         *  @ return wsuId.
         */
        return null;
    }//addID


    public EncryptedData encryptElement(
                String wsuElementID,
                Token token,
                String wsuEncryptedElementID,
                String encryptionAlgo
                ) {
        /*
         *  Find the element in the WSS message whose wsu:Id attribute matches with
         *  wsuElementID.
         *  XML encrypt the element using encryptionAlgo and key wrapped inside
         *  the token object.
         *  @ return the resulting EncryptedData object.
         */

        Element root = wssMessage.getDocumentElement();
        String xpathString = "//*[@Id=\"" + wsuElementID + "\"]";
        Element plainTextElement = null;
        try {
            plainTextElement = (Element)XPathAPI.selectSingleNode(root, xpathString);
        }//try
        catch ( Exception e ) {
            System.out.println ( "Exception in getting the plainTextElement." );
            e.printStackTrace();
        }//catch

        return new EncryptedData (
            encrypt (
                root,
                plainTextElement,//getElementByWSUId ( root, wsuElementID ),
                token,
                wsuEncryptedElementID,
                encryptionAlgo
                ),
            wsuEncryptedElementID,
            token,
            this
            );

    }//encryptElement


    public EncryptedData encryptElementWithXPath (
                String XPathExpression,
                Token token,
                String wsuEncryptedElementID,
                String encryptionAlgo
                ) {
        /*
         *  Find an element in the WSS message by applying
         *  XPathExpression on the WSS message.
         *  XML encrypt the element using cryptographic algorithm and key wrapped inside
         *  the token object.
         *  @ return the resulting EncryptedData object.
         */

        Element root = wssMessage.getDocumentElement();
        Element plainTextElement = null;
        try {
            plainTextElement =
                (Element)XPathAPI.selectSingleNode(root, XPathExpression);
        }
        catch ( Exception e ) {
            System.out.println ( "Exception in getting the plainTextElement." );
            e.printStackTrace();
        }
        return new EncryptedData (
            encrypt (
                root,
                plainTextElement,
                token,
                wsuEncryptedElementID,
                encryptionAlgo
                ),
            wsuEncryptedElementID,
            token,
            this
        );

    }//encryptElementWithXPath


    public String sign(
                String wsuElementID,
                Token token,
                String wsuSignatureID,
                String digestAlgo,
                String signatureAlgo,
                String canonicalizationAlgo
                ) {
        /*
         *  Find the element in the WSS message whose
         *  wsu:Id attribute matches with wsuElementID.
         *  XML sign the element using signatureAlgo and key wrapped inside
         *  the token object.
         *  Wrap the resulting ds:Signature element in a Signature object.
         *  @ return the Signature object.
         */
        return null;
    }//sign


    public String signWithXPath(
                String XPathExpression,
                Token token,
                String wsuSignatureID,
                String digestAlgo,
                String signatureAlgo,
                String canonicalizationAlgo
                ) {
        /*
         *  Find an element in the WSS message by applying XPathExpression on the WSS message.
         *  XML sign the element using cryptographic algorithm and key wrapped inside
         *  the token object.
         *  Wrap the resulting ds:Signature element in a Signature object.
         *  @ return the Signature object.
         */
        return null;
    }//sign


    public Token[] getAllTokens() {
        /*
         *  @ return the list of all tokens associated with the message.
         */
        return null;
    }//getAllTokens


    public String[] getAllSignatures() {
        /*
         *  @ return a list of all signatures associated with this WSS message.
         */
        return null;
    }//getAllSignatures


    public String[] getAllEncryptedData() {
        /*
         *  @ return a list of all EncryptedData structures associated with this WSS message.
         */
        return null;
    }//EncryptedData()


    protected Element encrypt (
                Element root,
                Element plainTextElement,
                Token token,
                String wsuEncryptedElementID,
                String encryptionAlgo
                ) {
        Element encryptedElement = null;

        if ( plainTextElement != null ){
            if ( token.getType().equals( "EncryptedKeyToken" ) ) {
                EncryptedKeyToken encToken = (EncryptedKeyToken)token;
                encryptedElement = encToken.encrypt ( 
                                     wsuEncryptedElementID, 
                                     encryptionAlgo, 
                                     plainTextElement );

                String encKey = encToken.getXMLString();
                Document sourceDoc = loadDocument ( encKey );
                addTokenToWSSEHeader ( sourceDoc, wssMessage );

            }//if

            else if ( token.getType().equals( "EncryptedKeyTokenWithCrossReference" ) ) {
                EncryptedKeyTokenWithCrossReference encTokenRef = 
                                                                           (EncryptedKeyTokenWithCrossReference)token;
                encryptedElement = encTokenRef.encrypt ( 
                                     wsuEncryptedElementID, 
                                     encryptionAlgo, 
                                     plainTextElement );

                String encKey = encTokenRef.getXMLString();
                addTokenToWSSEHeader ( loadDocument(encKey), wssMessage );

            }//else if

            else if ( token.getType().equals( "ReferenceListOnlyToken" ) ){
                ReferenceListOnlyToken listToken = (ReferenceListOnlyToken)token;
                encryptedElement = listToken.encrypt ( 
                                     wsuEncryptedElementID, 
                                     encryptionAlgo, 
                                     plainTextElement );
                String refList = listToken.getXMLString();
                addTokenToWSSEHeader ( loadDocument(refList), wssMessage );

            }//else if

        }//if
        return encryptedElement;
    }//encrypt


    private Document loadDocument ( String XMLString ) {
        DOMParser dp = new DOMParser();
        InputStream byteData = null;
        byteData = new ByteArrayInputStream (XMLString.getBytes());
            try{
                dp.parse ( new InputSource ( byteData ) );
            }catch ( Exception e ){
                System.out.println ( "The XMLString is not loaded." );
                e.printStackTrace();
            }

        return dp.getDocument();
    }//loadDocument


    private void addTokenToWSSEHeader (
                Document sourceDoc,/*token*/
                Document targetDoc /*wssMessage*/
                ) {
        try {
            Element header = (Element)targetDoc.getElementsByTagNameNS
                              ("http://www.w3.org/2001/12/soap-envelope",
                              "Header").item(0);

            if ( header == null )
            {
                 header = targetDoc.createElementNS("http://www.w3.org/2001/12/soap-envelope","Header" );
                 header.setAttribute(
                      "xmlns",
                      "http://www.w3.org/2001/12/soap-envelope"
                       );
                 targetDoc.getDocumentElement().insertBefore(
                       header,
                       targetDoc.getDocumentElement().getFirstChild()
                                             );
            }//if

            Element wsseSecurity = (Element)header.getElementsByTagNameNS
                                    ("http://schemas.xmlsoap.org/ws/2003/06/secext",
                                    "Security").item(0);

            if ( wsseSecurity == null )
            {
                wsseSecurity = targetDoc.createElementNS("http://schemas.xmlsoap.org/ws/2003/06/secext","Security" );
                wsseSecurity.setAttribute(
                      "xmlns",
                      "http://schemas.xmlsoap.org/ws/2003/06/secext"
                       );
                header.insertBefore(
                       wsseSecurity,
                       header.getFirstChild()
                               );
            }//if of wsseSecurity

            Element sourceDocEl = sourceDoc.getDocumentElement();
            Element wsseSecFirstEl = (Element) wsseSecurity.getFirstChild();

            wsseSecurity.insertBefore (
                       (Element) targetDoc.importNode(sourceDocEl, true),
                       wsseSecFirstEl );

        }catch ( Exception e ) {
            System.out.println ( "Exception in the addTokenToWSSEHeader..." );
            e.printStackTrace();
        }//catch

    }//addTokenToWSSEHeader

}//WSSClient