/*
 * Copyright  1999-2004 The Apache Software Foundation.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.xml.security.keys;



import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.crypto.SecretKey;

import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.content.KeyName;
import org.apache.xml.security.keys.content.KeyValue;
import org.apache.xml.security.keys.content.MgmtData;
import org.apache.xml.security.keys.content.PGPData;
import org.apache.xml.security.keys.content.RetrievalMethod;
import org.apache.xml.security.keys.content.SPKIData;
import org.apache.xml.security.keys.content.X509Data;
import org.apache.xml.security.keys.content.keyvalues.DSAKeyValue;
import org.apache.xml.security.keys.content.keyvalues.RSAKeyValue;
import org.apache.xml.security.keys.keyresolver.KeyResolver;
import org.apache.xml.security.keys.keyresolver.KeyResolverException;
import org.apache.xml.security.keys.keyresolver.KeyResolverSpi;
import org.apache.xml.security.keys.storage.StorageResolver;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.EncryptionConstants;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.IdResolver;
import org.apache.xml.security.utils.SignatureElementProxy;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * This class stand for KeyInfo Element that may contain keys, names,
 * certificates and other public key management information,
 * such as in-band key distribution or key agreement data.
 * <BR />
 * KeyInfo Element has two basic functions:
 * One is KeyResolve for getting the public key in signature validation processing.
 * the other one is toElement for getting the element in signature generation processing.
 * <BR />
 * The <CODE>lengthXXX()</CODE> methods provide access to the internal Key
 * objects:
 * <UL>
 * <LI>If the <CODE>KeyInfo</CODE> was constructed from an Element
 * (Signature verification), the <CODE>lengthXXX()</CODE> methods searches
 * for child elements of <CODE>ds:KeyInfo</CODE> for known types. </LI>
 * <LI>If the <CODE>KeyInfo</CODE> was constructed from scratch (during
 * Signature generation), the <CODE>lengthXXX()</CODE> methods return the number
 * of <CODE>XXXs</CODE> objects already passed to the KeyInfo</LI>
 * </UL>
 * <BR />
 * The <CODE>addXXX()</CODE> methods are used for adding Objects of the
 * appropriate type to the <CODE>KeyInfo</CODE>. This is used during signature
 * generation.
 * <BR />
 * The <CODE>itemXXX(int i)</CODE> methods return the i'th object of the
 * corresponding type.
 * <BR />
 * The <CODE>containsXXX()</CODE> methods return <I>whether</I> the KeyInfo
 * contains the corresponding type.
 *
 * @author $Author: raul $
 */
public class KeyInfo extends SignatureElementProxy {

   /** {@link org.apache.commons.logging} logging facility */
    static org.apache.commons.logging.Log log = 
        org.apache.commons.logging.LogFactory.getLog(KeyInfo.class.getName());
    List x509Datas=null;
    List encryptedKeys=null;
    
    static final List nullList;
    static {
    	List list = new ArrayList();
        list.add(null);
        nullList = Collections.unmodifiableList(list);
    }

   /**
    * Constructor KeyInfo
    * @param doc
    */
   public KeyInfo(Document doc) {

      super(doc);

      XMLUtils.addReturnToElement(this._constructionElement);
      
   }

   /**
    * Constructor KeyInfo
    *
    * @param element
    * @param BaseURI
    * @throws XMLSecurityException
    */
   public KeyInfo(Element element, String BaseURI) throws XMLSecurityException {
      super(element, BaseURI);
     // _storageResolvers.add(null);

   }

   /**
    * Sets the <code>Id</code> attribute
    *
    * @param Id ID
    */
   public void setId(String Id) {

      if ((Id != null)) {
         this._constructionElement.setAttributeNS(null, Constants._ATT_ID, Id);
         IdResolver.registerElementById(this._constructionElement, Id);
      }
   }

   /**
    * Returns the <code>Id</code> attribute
    *
    * @return the <code>Id</code> attribute
    */
   public String getId() {
      return this._constructionElement.getAttributeNS(null, Constants._ATT_ID);
   }

   /**
    * Method addKeyName
    *
    * @param keynameString
    */
   public void addKeyName(String keynameString) {
      this.add(new KeyName(this._doc, keynameString));
   }

   /**
    * Method add
    *
    * @param keyname
    */
   public void add(KeyName keyname) {

         this._constructionElement.appendChild(keyname.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method addKeyValue
    *
    * @param pk
    */
   public void addKeyValue(PublicKey pk) {
      this.add(new KeyValue(this._doc, pk));
   }

   /**
    * Method addKeyValue
    *
    * @param unknownKeyValueElement
    */
   public void addKeyValue(Element unknownKeyValueElement) {
      this.add(new KeyValue(this._doc, unknownKeyValueElement));
   }

   /**
    * Method add
    *
    * @param dsakeyvalue
    */
   public void add(DSAKeyValue dsakeyvalue) {
      this.add(new KeyValue(this._doc, dsakeyvalue));
   }

   /**
    * Method add
    *
    * @param rsakeyvalue
    */
   public void add(RSAKeyValue rsakeyvalue) {
      this.add(new KeyValue(this._doc, rsakeyvalue));
   }

   /**
    * Method add
    *
    * @param pk
    */
   public void add(PublicKey pk) {
      this.add(new KeyValue(this._doc, pk));
   }

   /**
    * Method add
    *
    * @param keyvalue
    */
   public void add(KeyValue keyvalue) {
         this._constructionElement.appendChild(keyvalue.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method addMgmtData
    *
    * @param mgmtdata
    */
   public void addMgmtData(String mgmtdata) {
      this.add(new MgmtData(this._doc, mgmtdata));
   }

   /**
    * Method add
    *
    * @param mgmtdata
    */
   public void add(MgmtData mgmtdata) {
         this._constructionElement.appendChild(mgmtdata.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method addPGPData
    *
    * @param pgpdata
    */
   public void add(PGPData pgpdata) {
         this._constructionElement.appendChild(pgpdata.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method addRetrievalMethod
    *
    * @param URI
    * @param transforms
    * @param Type  
    */
   public void addRetrievalMethod(String URI, Transforms transforms,
                                  String Type) {
      this.add(new RetrievalMethod(this._doc, URI, transforms, Type));
   }

   /**
    * Method add
    *
    * @param retrievalmethod
    */
   public void add(RetrievalMethod retrievalmethod) {
         this._constructionElement.appendChild(retrievalmethod.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method add
    *
    * @param spkidata
    */
   public void add(SPKIData spkidata) {
         this._constructionElement.appendChild(spkidata.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method addX509Data
    *
    * @param x509data
    */
   public void add(X509Data x509data) {
    	  if (x509Datas==null)
    		  x509Datas=new ArrayList();
    	  x509Datas.add(x509data);
         this._constructionElement.appendChild(x509data.getElement());
         XMLUtils.addReturnToElement(this._constructionElement);
   }

	/**
	 * Method addEncryptedKey
	 *
	 * @param encryptedKey
	 * @throws XMLEncryptionException
	 */

	public void add(EncryptedKey encryptedKey) 
		throws XMLEncryptionException {
			if (encryptedKeys==null)
				encryptedKeys=new ArrayList();
			encryptedKeys.add(encryptedKey);
			XMLCipher cipher = XMLCipher.getInstance();
			this._constructionElement.appendChild(cipher.martial(encryptedKey));
	}

   /**
    * Method addUnknownElement
    *
    * @param element
    */
   public void addUnknownElement(Element element) {
         this._constructionElement.appendChild(element);
         XMLUtils.addReturnToElement(this._constructionElement);
   }

   /**
    * Method lengthKeyName
    *
    * @return the number of the KeyName tags
    */
   public int lengthKeyName() {
      return this.length(Constants.SignatureSpecNS, Constants._TAG_KEYNAME);
   }

   /**
    * Method lengthKeyValue
    *
    *@return the number of the KeyValue tags
    */
   public int lengthKeyValue() {
      return this.length(Constants.SignatureSpecNS, Constants._TAG_KEYVALUE);
   }

   /**
    * Method lengthMgmtData
    *
    *@return the number of the MgmtData tags
    */
   public int lengthMgmtData() {
      return this.length(Constants.SignatureSpecNS, Constants._TAG_MGMTDATA);
   }

   /**
    * Method lengthPGPData
    *
    *@return the number of the PGPDat. tags
    */
   public int lengthPGPData() {
      return this.length(Constants.SignatureSpecNS, Constants._TAG_PGPDATA);
   }

   /**
    * Method lengthRetrievalMethod
    *
    *@return the number of the RetrievalMethod tags
    */
   public int lengthRetrievalMethod() {
      return this.length(Constants.SignatureSpecNS,
                         Constants._TAG_RETRIEVALMETHOD);
   }

   /**
    * Method lengthSPKIData
    *
    *@return the number of the SPKIData tags
    */
   public int lengthSPKIData() {
      return this.length(Constants.SignatureSpecNS, Constants._TAG_SPKIDATA);
   }

   /**
    * Method lengthX509Data
    *
    *@return the number of the X509Data tags
    */
   public int lengthX509Data() {
	   if (x509Datas!=null) {
		   return x509Datas.size(); 
	   }
      return this.length(Constants.SignatureSpecNS, Constants._TAG_X509DATA);
   }

   /**
    * Method lengthUnknownElement
    * NOTE posibly buggy.
    *@return the number of the UnknownElement tags
    */
   public int lengthUnknownElement() {

      int res = 0;
      NodeList nl = this._constructionElement.getChildNodes();

      for (int i = 0; i < nl.getLength(); i++) {
         Node current = nl.item(i);

         /**
          * $todo$ using this method, we don't see unknown Elements
          *  from Signature NS; revisit
          */
         if ((current.getNodeType() == Node.ELEMENT_NODE)
                 && current.getNamespaceURI()
                    .equals(Constants.SignatureSpecNS)) {
            res++;
         }
      }

      return res;
   }

   /**
    * Method itemKeyName
    *
    * @param i
    * @return the asked KeyName element, null if the index is too big
    * @throws XMLSecurityException
    */
   public KeyName itemKeyName(int i) throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_KEYNAME,i);

      if (e != null) {
         return new KeyName(e, this._baseURI);
      } 
      return null;      
   }

   /**
    * Method itemKeyValue
    *
    * @param i
    * @return the asked KeyValue element, null if the index is too big
    * @throws XMLSecurityException
    */
   public KeyValue itemKeyValue(int i) throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_KEYVALUE,i);

      if (e != null) {
         return new KeyValue(e, this._baseURI);
      } 
      return null;      
   }

   /**
    * Method itemMgmtData
    *
    * @param i
    *@return the asked MgmtData element, null if the index is too big
    * @throws XMLSecurityException
    */
   public MgmtData itemMgmtData(int i) throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_MGMTDATA,i);

      if (e != null) {
         return new MgmtData(e, this._baseURI);
      } 
       return null;      
   }

   /**
    * Method itemPGPData
    *
    * @param i
    *@return the asked PGPData element, null if the index is too big
    * @throws XMLSecurityException
    */
   public PGPData itemPGPData(int i) throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_PGPDATA,i);

      if (e != null) {
         return new PGPData(e, this._baseURI);
      } 
      return null;      
   }

   /**
    * Method itemRetrievalMethod
    *
    * @param i
    *@return the asked RetrievalMethod element, null if the index is too big
    * @throws XMLSecurityException
    */
   public RetrievalMethod itemRetrievalMethod(int i)
           throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_RETRIEVALMETHOD,i);

      if (e != null) {
         return new RetrievalMethod(e, this._baseURI);
      } 
      return null;
   }

   /**
    * Method itemSPKIData
    *
    * @param i
    *@return the asked SPKIData element, null if the index is too big
    * @throws XMLSecurityException
    */
   public SPKIData itemSPKIData(int i) throws XMLSecurityException {

      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_SPKIDATA,i);

      if (e != null) {
         return new SPKIData(e, this._baseURI);
      } 
      return null;     
   }

   /**
    * Method itemX509Data
    *@return the asked X509Data element, null if the index is too big
    * @param i
    *
    * @throws XMLSecurityException
    */
   public X509Data itemX509Data(int i) throws XMLSecurityException {
	   if (x509Datas!=null) {
		   return (X509Data) x509Datas.get(i); 
	   }
      Element e = XMLUtils.selectDsNode(this._constructionElement.getFirstChild(),
                                                Constants._TAG_X509DATA,i);

      if (e != null) {
         return new X509Data(e, this._baseURI);
      } 
      return null;
   }

   /**
	* Method itemEncryptedKey
	*
	* @param i
	* @return the asked EncryptedKey element, null if the index is too big
	* @throws XMLSecurityException
	*/

	public EncryptedKey itemEncryptedKey(int i) throws XMLSecurityException {
		if (encryptedKeys!=null) {
			return (EncryptedKey) encryptedKeys.get(i);
		}
		Element e = 
			XMLUtils.selectXencNode(this._constructionElement.getFirstChild(),
										  EncryptionConstants._TAG_ENCRYPTEDKEY,i);

		if (e != null) {
			XMLCipher cipher = XMLCipher.getInstance();
			cipher.init(XMLCipher.UNWRAP_MODE, null);
			return cipher.loadEncryptedKey(e);
		}
		return null;
   }

   /**
    * Method itemUnknownElement
    *
    * @param i index
    * @return the element number of the unknown elemens
    */
   public Element itemUnknownElement(int i) {

      NodeList nl = this._constructionElement.getChildNodes();
      int res = 0;

      for (int j = 0; j < nl.getLength(); j++) {
         Node current = nl.item(j);

         /**
          * $todo$ using this method, we don't see unknown Elements
          *  from Signature NS; revisit
          */
         if ((current.getNodeType() == Node.ELEMENT_NODE)
                 && current.getNamespaceURI()
                    .equals(Constants.SignatureSpecNS)) {
            res++;

            if (res == i) {
               return (Element) current;
            }
         }
      }

      return null;
   }

   /**
    * Method isEmpty
    *
    * @return true if the element has no descedants.
    */
   public boolean isEmpty() {
      return this._constructionElement.getFirstChild()==null;
   }

   /**
    * Method containsKeyName
    *
    * @return If the KeyInfo contains a KeyName node
    */
   public boolean containsKeyName() {
      return this.lengthKeyName() > 0;
   }

   /**
    * Method containsKeyValue
    *
    * @return If the KeyInfo contains a KeyValue node
    */
   public boolean containsKeyValue() {
      return this.lengthKeyValue() > 0;
   }

   /**
    * Method containsMgmtData
    *
    * @return If the KeyInfo contains a MgmtData node
    */
   public boolean containsMgmtData() {
      return this.lengthMgmtData() > 0;
   }

   /**
    * Method containsPGPData
    *
    * @return If the KeyInfo contains a PGPData node
    */
   public boolean containsPGPData() {
      return this.lengthPGPData() > 0;
   }

   /**
    * Method containsRetrievalMethod
    *
    * @return If the KeyInfo contains a RetrievalMethod node
    */
   public boolean containsRetrievalMethod() {
      return this.lengthRetrievalMethod() > 0;
   }

   /**
    * Method containsSPKIData
    *
    * @return If the KeyInfo contains a SPKIData node
    */
   public boolean containsSPKIData() {
      return this.lengthSPKIData() > 0;
   }

   /**
    * Method containsUnknownElement
    *
    * @return If the KeyInfo contains a UnknownElement node
    */
   public boolean containsUnknownElement() {
      return this.lengthUnknownElement() > 0;
   }

   /**
    * Method containsX509Data
    *
    * @return If the KeyInfo contains a X509Data node
    */
   public boolean containsX509Data() {
      return this.lengthX509Data() > 0;
   }

   /**
    * This method returns the public key.
    *
    * @return If the KeyInfo contains a PublicKey node
    * @throws KeyResolverException
    */

   public PublicKey getPublicKey() throws KeyResolverException {

      PublicKey pk = this.getPublicKeyFromInternalResolvers();

      if (pk != null) {
         log.debug("I could find a key using the per-KeyInfo key resolvers");

         return pk;
      } 
      log.debug("I couldn't find a key using the per-KeyInfo key resolvers");      

      pk = this.getPublicKeyFromStaticResolvers();

      if (pk != null) {
         log.debug("I could find a key using the system-wide key resolvers");

         return pk;
      }
      log.debug("I couldn't find a key using the system-wide key resolvers");      

      return null;
   }

   /**
    * Searches the library wide keyresolvers for public keys
    *
    * @return The publick contained in this Node.
    * @throws KeyResolverException
    */
   PublicKey getPublicKeyFromStaticResolvers() throws KeyResolverException {
	  int length=KeyResolver.length();
	  int storageLength=this._storageResolvers.size();
	  Iterator it= KeyResolver.iterator();
      for (int i = 0; i < length; i++) {
         KeyResolverSpi keyResolver = (KeyResolverSpi) it.next();
         Node currentChild=this._constructionElement.getFirstChild();
         String uri= this.getBaseURI();
         while (currentChild!=null)      {       
            if (currentChild.getNodeType() == Node.ELEMENT_NODE) {          	  
                  for (int k = 0; k < storageLength; k++) {
                     StorageResolver storage =
                        (StorageResolver) this._storageResolvers.get(k);

                     PublicKey pk =
                           keyResolver.engineLookupAndResolvePublicKey((Element) currentChild,
                                                       uri,
                                                        storage);

                     if (pk != null) {
                    	 KeyResolver.hit(it);
                         return pk;
                     }                     
                  }                              
            }
            currentChild=currentChild.getNextSibling();
         }      
      }
      return null;
   }

   /**
    * Searches the per-KeyInfo keyresolvers for public keys
    *
    * @return The publick contained in this Node.
    * @throws KeyResolverException
    */
   PublicKey getPublicKeyFromInternalResolvers() throws KeyResolverException {
	  int length=lengthInternalKeyResolver();
	  int storageLength=this._storageResolvers.size();
      for (int i = 0; i < length; i++) {
         KeyResolverSpi keyResolver = this.itemInternalKeyResolver(i);
         if (log.isDebugEnabled())
         	log.debug("Try " + keyResolver.getClass().getName());

         Node currentChild=this._constructionElement.getFirstChild();
         String uri=this.getBaseURI();
         while (currentChild!=null)      {    
            if (currentChild.getNodeType() == Node.ELEMENT_NODE) {               
               for (int k = 0; k < storageLength; k++) {
                   StorageResolver storage =
                      (StorageResolver) this._storageResolvers.get(k);
                   PublicKey pk = keyResolver
                           .engineLookupAndResolvePublicKey((Element) currentChild, uri, storage);

                     if (pk != null) {
                         return pk;
                     }                     
                  }               
            }
            currentChild=currentChild.getNextSibling();
         }
      }

      return null;
   }

   /**
    * Method getX509Certificate
    *
    * @return The certificate contined in this KeyInfo
    * @throws KeyResolverException
    */
   public X509Certificate getX509Certificate() throws KeyResolverException {

      // First search using the individual resolvers from the user
      X509Certificate cert = this.getX509CertificateFromInternalResolvers();

      if (cert != null) {
         log.debug(
            "I could find a X509Certificate using the per-KeyInfo key resolvers");

         return cert;
      } 
      log.debug(
            "I couldn't find a X509Certificate using the per-KeyInfo key resolvers");
      

      // Then use the system-wide Resolvers
      cert = this.getX509CertificateFromStaticResolvers();

      if (cert != null) {
         log.debug(
            "I could find a X509Certificate using the system-wide key resolvers");

         return cert;
      } 
      log.debug(
            "I couldn't find a X509Certificate using the system-wide key resolvers");
      

      return null;
   }

   /**
    * This method uses each System-wide {@link KeyResolver} to search the
    * child elements. Each combination of {@link KeyResolver} and child element
    * is checked against all {@link StorageResolver}s.
    *
    * @return The certificate contined in this KeyInfo
    * @throws KeyResolverException
    */
   X509Certificate getX509CertificateFromStaticResolvers()
           throws KeyResolverException {
      if (log.isDebugEnabled())
      	log.debug("Start getX509CertificateFromStaticResolvers() with "
                + KeyResolver.length() + " resolvers");
      String uri=this.getBaseURI();
      int length= KeyResolver.length();
      int storageLength=this._storageResolvers.size();   
      Iterator it = KeyResolver.iterator();
      for (int i = 0; i <length; i++) {
         KeyResolverSpi keyResolver = (KeyResolverSpi) it.next();
         X509Certificate cert= applyCurrentResolver(uri, storageLength, keyResolver);
         if (cert!=null) {
        	 KeyResolver.hit(it);
        	 return cert;
         }
      }
      return null;
   }

   private X509Certificate applyCurrentResolver(String uri, int storageLength, KeyResolverSpi keyResolver) throws KeyResolverException {
	   Node currentChild=this._constructionElement.getFirstChild();
	   while (currentChild!=null)      {       
		   if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
	       for (int k = 0; k < storageLength; k++) {
	           StorageResolver storage =
	              (StorageResolver) this._storageResolvers.get(k);

	           X509Certificate cert = keyResolver
	                .engineLookupResolveX509Certificate((Element) currentChild, uri,
	                		storage);

	           if (cert != null) {                	   
	               return cert;
	          }                  
	       }               
	    }
	    currentChild=currentChild.getNextSibling();
	 }
	 return null;
   }

   /**
    * Method getX509CertificateFromInternalResolvers
    *
    * @return The certificate contined in this KeyInfo
    * @throws KeyResolverException
    */
   X509Certificate getX509CertificateFromInternalResolvers()
           throws KeyResolverException {
      if (log.isDebugEnabled())
      	log.debug("Start getX509CertificateFromInternalResolvers() with "
                + this.lengthInternalKeyResolver() + " resolvers");
      String uri=this.getBaseURI();
      int storageLength=this._storageResolvers.size();
      for (int i = 0; i < this.lengthInternalKeyResolver(); i++) {
         KeyResolverSpi keyResolver = this.itemInternalKeyResolver(i);
         if (log.isDebugEnabled())
         	log.debug("Try " + keyResolver.getClass().getName());
         X509Certificate cert= applyCurrentResolver(uri, storageLength, keyResolver);
         if (cert!=null) {        	
        	 return cert;
         }      
      }

      return null;
   }

   /**
    * This method returns a secret (symmetric) key. This is for XML Encryption.
    * @return the secret key contained in this KeyInfo
    * @throws KeyResolverException
    */
   public SecretKey getSecretKey() throws KeyResolverException {
      SecretKey sk = this.getSecretKeyFromInternalResolvers();

      if (sk != null) {
         log.debug("I could find a secret key using the per-KeyInfo key resolvers");

         return sk;
      } 
      log.debug("I couldn't find a secret key using the per-KeyInfo key resolvers");
      

      sk = this.getSecretKeyFromStaticResolvers();

      if (sk != null) {
         log.debug("I could find a secret key using the system-wide key resolvers");

         return sk;
      } 
      log.debug("I couldn't find a secret key using the system-wide key resolvers");
      

      return null;
   }

   /**
    * Searches the library wide keyresolvers for Secret keys
    *
    * @return the secret key contained in this KeyInfo
    * @throws KeyResolverException
    */

   SecretKey getSecretKeyFromStaticResolvers() throws KeyResolverException {
	  final int length=KeyResolver.length();
	  int storageLength=this._storageResolvers.size();
	  Iterator it = KeyResolver.iterator();
      for (int i = 0; i < length; i++) {
         KeyResolverSpi keyResolver = (KeyResolverSpi) it.next();

         Node currentChild=this._constructionElement.getFirstChild();
         String uri=this.getBaseURI();
         while (currentChild!=null)      {    
            if (currentChild.getNodeType() == Node.ELEMENT_NODE) {   
                  for (int k = 0; k < storageLength; k++) {
                     StorageResolver storage =
                        (StorageResolver) this._storageResolvers.get(k);

                     SecretKey sk =
                           keyResolver.engineLookupAndResolveSecretKey((Element) currentChild,
                                                        uri,
                                                        storage);

                     if (sk != null) {
                        return sk;
                     }                     
                  }               
            }
            currentChild=currentChild.getNextSibling();
         }
      }
      return null;
   }

   /**
    * Searches the per-KeyInfo keyresolvers for secret keys
    *
    * @return the secret key contained in this KeyInfo
    * @throws KeyResolverException
    */

   SecretKey getSecretKeyFromInternalResolvers() throws KeyResolverException {
	   int storageLength=this._storageResolvers.size();
      for (int i = 0; i < this.lengthInternalKeyResolver(); i++) {
         KeyResolverSpi keyResolver = this.itemInternalKeyResolver(i);
         if (log.isDebugEnabled())
         	log.debug("Try " + keyResolver.getClass().getName());

         Node currentChild=this._constructionElement.getFirstChild();
         String uri=this.getBaseURI();
         while (currentChild!=null)      {    
            if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
               for (int k = 0; k < storageLength; k++) {
                     StorageResolver storage =
                        (StorageResolver) this._storageResolvers.get(k);

                     SecretKey sk = keyResolver
                           .engineLookupAndResolveSecretKey((Element) currentChild, uri, storage);

                     if (sk != null) {
                        return sk;
                     }                    
                }
             }            
            currentChild=currentChild.getNextSibling();
         }
      }

      return null;
   }

   /**
    * Stores the individual (per-KeyInfo) {@link KeyResolver}s
    */
   List _internalKeyResolvers = null;

   /**
    * This method is used to add a custom {@link KeyResolverSpi} to a KeyInfo
    * object.
    *
    * @param realKeyResolver
    */
   public void registerInternalKeyResolver(KeyResolverSpi realKeyResolver) {
	   if (_internalKeyResolvers==null) {
		   _internalKeyResolvers=new ArrayList();
	   }
      this._internalKeyResolvers.add(realKeyResolver);
   }

   /**
    * Method lengthInternalKeyResolver
    * @return the length of the key
    */
   int lengthInternalKeyResolver() {
	   if (_internalKeyResolvers==null)
		   return 0;
      return this._internalKeyResolvers.size();
   }

   /**
    * Method itemInternalKeyResolver
    *
    * @param i the index
    * @return the KeyResolverSpi for the index.
    */
   KeyResolverSpi itemInternalKeyResolver(int i) {
      return (KeyResolverSpi) this._internalKeyResolvers.get(i);
   }

   /** Field _storageResolvers */
   List _storageResolvers = nullList;

   /**
    * Method addStorageResolver
    *
    * @param storageResolver
    */
   public void addStorageResolver(StorageResolver storageResolver) {
	   if  (_storageResolvers == nullList  ){
		   _storageResolvers=new ArrayList();
	   }      
         this._storageResolvers.add(storageResolver);
      
   }

   //J-
   static boolean _alreadyInitialized = false;
   /** init the keyinfo (Still needed?)*/
   public static void init() {

      if (!KeyInfo._alreadyInitialized) {
         if (KeyInfo.log == null) {

            /**
             * $todo$ why the hell does the static initialization from the
             *  start not work ?
             */
            KeyInfo.log =
                    org.apache.commons.logging.LogFactory.getLog(KeyInfo.class.getName());

            log.error("Had to assign log in the init() function");
         }

         // KeyInfo._contentHandlerHash = new HashMap(10);
         KeyInfo._alreadyInitialized = true;
      }
   }

   /** @inheritDoc */
   public String getBaseLocalName() {
      return Constants._TAG_KEYINFO;
   }
}
