/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "<WebSig>" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 2001, Institute for
 * Data Communications Systems, <http://www.nue.et-inf.uni-siegen.de/>.
 * The development of this software was partly funded by the European
 * Commission in the <WebSig> project in the ISIS Programme.
 * For more information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.xml.security.algorithms;



import java.security.Key;
import java.security.Provider;
import java.security.Security;

import javax.xml.transform.TransformerException;

import org.apache.xml.security.utils.XMLUtils;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;


/**
 * This class maps algorithm identifier URIs to JAVA JCE class names.
 *
 * @author $Author$
 */
public class JCEMapper {

   /** {@link org.apache.commons.logging} logging facility */
    static org.apache.commons.logging.Log log = 
        org.apache.commons.logging.LogFactory.getLog(JCEMapper.class.getName());

   /** Field _providerList */
   private static Element _providerList = null;

   /** Field _nscontext */
   private static Element _nscontext = null;

   /**
    * Method init
    *
    * @param mappingElement
    * @throws Exception
    */
   public static void init(Element mappingElement) throws Exception {

      JCEMapper._providerList = mappingElement;

      Document doc = mappingElement.getOwnerDocument();

      JCEMapper._nscontext =
         XMLUtils.createDSctx(doc, "x",
                              "http://www.xmlsecurity.org/NS/#configuration");
   }

   /**
    * This method takes a Provider ID and tries to register this provider in the JCE.
    *
    * @param Id
    *
    */
   public static boolean addProvider(String Id) {

      try {
         if (Security.getProvider(Id) == null) {
            Element providerElem = (Element) XPathAPI.selectSingleNode(
               JCEMapper._providerList,
               "./x:Providers/x:Provider[@Id='" + Id + "']",
               JCEMapper._nscontext);
            String providerClass = providerElem.getAttributeNS(null, "Class");
            java.security.Provider prov =
               (java.security.Provider) Class.forName(providerClass)
                  .newInstance();

            if (java.security.Security.getProvider(Id) == null) {
               log.debug("The provider " + Id
                         + " had to be added to the java.security.Security");
               java.security.Security.addProvider(prov);

               Provider registeredProvider =
                  java.security.Security.getProvider(Id);

               if (registeredProvider != null) {
                  return true;
               }
            }
         }
      } catch (TransformerException ex) {}
      catch (ClassNotFoundException ex) {}
      catch (IllegalAccessException ex) {}
      catch (InstantiationException ex) {}

      return false;
   }

   /**
    * Method getProviderIsAvailable
    *
    * @param providerId
    *
    */
   public static boolean getProviderIsInClassPath(String providerId) {

      boolean available = false;

      try {
         Element pro =
            (Element) XPathAPI.selectSingleNode(JCEMapper._providerList,
                                                "./x:Providers/x:Provider[@Id='"
                                                + providerId + "']",
                                                JCEMapper._nscontext);
         String providerClass = pro.getAttributeNS(null, "Class");
         java.security.Provider prov =
            (java.security.Provider) Class.forName(providerClass).newInstance();

         if (prov != null) {
            available = true;
         }
      } catch (TransformerException ex) {
         ;
      } catch (ClassNotFoundException ex) {
         ;
      } catch (IllegalAccessException ex) {
         ;
      } catch (InstantiationException ex) {
         ;
      }

      return available;
   }

   /**
    * Return <CODE>true</CODE> if the Provider with the given
    * <CODE>providerId</CODE> is available in {@link java.security.Security}.
    *
    * @param providerId
    * @return <CODE>true</CODE> if the Provider with the given <CODE>providerId</CODE> is available in {@link java.security.Security}
    */
   public static boolean getProviderIsRegisteredAtSecurity(String providerId) {

      java.security.Provider prov =
         java.security.Security.getProvider(providerId);

      if (prov != null) {
         return true;
      }

      return false;
   }

   /**
    * Method translateURItoJCEID
    *
    * @param AlgorithmURI
    *
    */
   public static ProviderIdClass translateURItoJCEID(String AlgorithmURI) {

      log.debug("Request for URI " + AlgorithmURI);

      try {

         /*
         Attr jceName = (Attr) XPathAPI.selectSingleNode(
            JCEMapper._providerList,
            "./x:Algorithms/x:Algorithm[@URI='" + AlgorithmURI
            + "']/x:Provider[1]/@JCEName", JCEMapper._nscontext);
         */
         NodeList providers = XPathAPI.selectNodeList(JCEMapper._providerList,
                                 "./x:Algorithms/x:Algorithm[@URI='"
                                 + AlgorithmURI + "']/x:ProviderAlgo",
                                 JCEMapper._nscontext);

         for (int i = 0; i < providers.getLength(); i++) {
            Element pro = (Element) providers.item(i);
            String jceName = pro.getAttributeNS(null, "JCEName");
            String providerId = pro.getAttributeNS(null, "ProviderId");

            if (JCEMapper.getProviderIsInClassPath(providerId)) {
               JCEMapper.addProvider(providerId);

               ProviderIdClass result = new ProviderIdClass(jceName,
                                           providerId);

               log.debug("Found " + result.getAlgorithmID() + " from provider "
                         + result.getProviderId());

               return result;
            }
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return null;
   }

   /**
    * Method translateURItoJCEID
    *
    * @param AlgorithmURI
    * @param requestedProviderId
    *
    */
   public static ProviderIdClass translateURItoJCEID(String AlgorithmURI,
           String requestedProviderId) {

      log.debug("Request for URI " + AlgorithmURI + " from provider "
                + requestedProviderId);

      if (!JCEMapper.getProviderIsInClassPath(requestedProviderId)) {
         return null;
      }

      try {
         Element pro = (Element) XPathAPI.selectSingleNode(
            JCEMapper._providerList,
            "./x:Algorithms/x:Algorithm[@URI='" + AlgorithmURI
            + "']/x:ProviderAlgo[@ProviderId='" + requestedProviderId + "']",
            JCEMapper._nscontext);
         String jceName = pro.getAttributeNS(null, "JCEName");

         JCEMapper.addProvider(requestedProviderId);

         ProviderIdClass result = new ProviderIdClass(jceName,
                                     requestedProviderId);

         log.debug("Found " + result.getAlgorithmID() + " from provider "
                   + result.getProviderId());

         return result;
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return null;
   }

   /**
    * Method getAlgorithmClassFromURI
    *
    * @param AlgorithmURI
    *
    */
   public static String getAlgorithmClassFromURI(String AlgorithmURI) {

      log.debug("Request for URI " + AlgorithmURI);

      try {
         NodeList providers = XPathAPI.selectNodeList(JCEMapper._providerList,
                                 "./x:Algorithms/x:Algorithm[@URI='"
                                 + AlgorithmURI + "']/x:ProviderAlgo",
                                 JCEMapper._nscontext);

         for (int i = 0; i < providers.getLength(); i++) {
            Element pro = (Element) providers.item(i);
            Attr jceName = pro.getAttributeNodeNS(null, "JCEName");

            log.debug("Found " + jceName.getNodeValue());
         }

         return ((Element) providers.item(0)).getAttributeNS(null, "JCEName");
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());

         return "";
      }
   }

   /**
    * Method getKeyTypeFromURI
    *
    * @param AlgorithmURI
    *
    */
   public static int getKeyTypeFromURI(String AlgorithmURI) {

      try {
         Attr algoclassAttr =
            (Attr) XPathAPI.selectSingleNode(JCEMapper._providerList,
                                             "./x:Algorithms/x:Algorithm[@URI='"
                                             + AlgorithmURI
                                             + "']/@AlgorithmClass", JCEMapper
                                                ._nscontext);

         if (algoclassAttr == null) {
            return -1;
         } else {
            String algoclass = algoclassAttr.getNodeValue();

            if (algoclass.equals(JCEMapper.KEYTYPE_BLOCK_ENCRYPTION)) {
               return javax.crypto.Cipher.SECRET_KEY;
            } else if (algoclass.equals("Mac")) {
               return javax.crypto.Cipher.SECRET_KEY;
            } else if (algoclass.equals(JCEMapper.KEYTYPE_SYMMETRIC_KEY_WRAP)) {
               return javax.crypto.Cipher.SECRET_KEY;
            } else if (algoclass.equals(JCEMapper.KEYTYPE_KEY_TRANSPORT)) {
               return javax.crypto.Cipher.SECRET_KEY;
            }
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return -1;
   }

   /**
    * Returns the keylength in bit for a particular algorithm.
    *
    * @param AlgorithmURI
    *
    */
   public static int getKeyLengthFromURI(String AlgorithmURI) {

      try {
         Attr algoclassAttr =
            (Attr) XPathAPI.selectSingleNode(JCEMapper._providerList,
                                             "./x:Algorithms/x:Algorithm[@URI='"
                                             + AlgorithmURI + "']/@KeyLength",
                                             JCEMapper._nscontext);

         if (algoclassAttr != null) {
            return Integer.parseInt(algoclassAttr.getNodeValue());
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return 0;
   }

   /**
    * Method getJCEKeyAlgorithmFromURI
    *
    * @param AlgorithmURI
    * @param ProviderId
    *
    */
   public static String getJCEKeyAlgorithmFromURI(String AlgorithmURI,
           String ProviderId) {

      try {
         Attr algoclassAttr =
            (Attr) XPathAPI.selectSingleNode(JCEMapper._providerList,
                                             "./x:Algorithms/x:Algorithm[@URI='"
                                             + AlgorithmURI
                                             + "']/x:ProviderAlgo[@ProviderId='"
                                             + ProviderId + "']/@RequiredKey",
                                             JCEMapper._nscontext);

         if (algoclassAttr != null) {
            return algoclassAttr.getNodeValue();
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return null;
   }


   public static String getJCEIVAlgorithmFromURI(String AlgorithmURI,
           String ProviderId) {

      try {
         Attr algoclassAttr =
            (Attr) XPathAPI.selectSingleNode(JCEMapper._providerList,
                                             "./x:Algorithms/x:Algorithm[@URI='"
                                             + AlgorithmURI
                                             + "']/x:ProviderAlgo[@ProviderId='"
                                             + ProviderId + "']/@IVJCEName",
                                             JCEMapper._nscontext);

         if (algoclassAttr != null) {
            return algoclassAttr.getNodeValue();
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return null;
   }

   /** Field KEYTYPE_SYMMETRIC_KEY_WRAP           */
   public static final String KEYTYPE_SYMMETRIC_KEY_WRAP = "SymmetricKeyWrap";

   /** Field KEYTYPE_BLOCK_ENCRYPTION           */
   public static final String KEYTYPE_BLOCK_ENCRYPTION = "BlockEncryption";

   /** Field KEYTYPE_KEY_TRANSPORT           */
   public static final String KEYTYPE_KEY_TRANSPORT = "KeyTransport";

   /**
    * This takes a {@link Key} and one of the <CODE>JCEMapper.KEYTYPE_XXX</CODE>
    * Strings and returns the algorithm for which this key is.
    * <BR />
    * Example: If you enter an AES Key of length 128 bit and the
    * <CODE>JCEMapper.KEYTYPE_SYMMETRIC_KEY_WRAP</CODE>, the result is
    * <CODE>EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128</CODE>.
    *
    *
    * @param key
    * @param type
    *
    */
   public static String getURIfromKey(Key key, String type) {

      String JCEalgo = key.getAlgorithm();
      String keyLength = new Integer(key.getEncoded().length * 8).toString();

      try {
         Attr URI = (Attr) XPathAPI.selectSingleNode(
            JCEMapper._providerList,
            "./x:Algorithms/x:Algorithm[@KeyLength='" + keyLength
            + "' and @AlgorithmClass='" + type
            + "']/x:ProviderAlgo[@RequiredKey='" + JCEalgo + "']/../@URI",
            JCEMapper._nscontext);

         if (URI != null) {
            return URI.getNodeValue();
         }
      } catch (TransformerException ex) {
         log.debug("Found nothing: " + ex.getMessage());
      }

      return null;
   }

   /*
   public static String getWrapURIfromKey(Key key) {
      return JCEMapper.getURIfromKey(key, JCEMapper.KEYTYPE_SYMMETRIC_KEY_WRAP);
   }

   public static String getCipherURIfromKey(Key key) {
      return JCEMapper.getURIfromKey(key, JCEMapper.KEYTYPE_BLOCK_ENCRYPTION);
   }
   */

   /**
    * Class ProviderIdClass
    *
    * @author $Author$
    * @version $Revision$
    */
   public static class ProviderIdClass {

      /** Field _jceid */
      private String _algorithmId;

      /** Field _providerClass */
      private String _providerId;

      /**
       * Constructor ProviderIdClass
       *
       * @param AlgorithmID
       * @param ProviderId
       */
      protected ProviderIdClass(String AlgorithmID, String ProviderId) {
         this._algorithmId = AlgorithmID;
         this._providerId = ProviderId;
      }

      /**
       * Method getJceId
       *
       *
       */
      public String getAlgorithmID() {
         return this._algorithmId;
      }

      /**
       * Method getProvider
       *
       *
       */
      public String getProviderId() {
         return this._providerId;
      }
   }
}
