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
       }//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