/*
 * 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.utils;




import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.Arrays;
import java.util.WeakHashMap;
import java.lang.ref.WeakReference;


/**
 * Purpose of this class is to enable the XML Parser to keep track of ID
 * attributes. This is done by 'registering' attributes of type ID at the
 * IdResolver. This is necessary if we create a document from scratch and we
 * sign some resources with a URI using a fragent identifier...
 * <BR />
 * The problem is that if you do not validate a document, you cannot use the
 * <CODE>getElementByID</CODE> functionality. So this modules uses some implicit
 * knowledge on selected Schemas and DTDs to pick the right Element for a given
 * ID: We know that all <CODE>@Id</CODE> attributes in an Element from the XML
 * Signature namespace are of type <CODE>ID</CODE>.
 *
 * @author $Author$
 * @see <A HREF="http://www.xml.com/lpt/a/2001/11/07/id.html">"Identity Crisis" on xml.com</A>
 */
public class IdResolver {

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

   static WeakHashMap docMap = new WeakHashMap();
    
   /**
    * Constructor IdResolver
    *
    */
   private IdResolver() {

      // we don't allow instantiation
   }

   /**
    * Method registerElementById
    *
    * @param element
    * @param idValue
    */
   public static void registerElementById(Element element, String idValue) {
      Document doc = element.getOwnerDocument();
      WeakHashMap elementMap = (WeakHashMap) docMap.get(doc);
      if(elementMap == null) {
          elementMap = new WeakHashMap();
          docMap.put(doc, elementMap);
      }
      elementMap.put(idValue, new WeakReference(element));
   }

   /**
    * Method registerElementById
    *
    * @param element
    * @param id
    */
   public static void registerElementById(Element element, Attr id) {
      IdResolver.registerElementById(element, id.getNodeValue());
   }

   /**
    * Method getElementById
    *
    * @param doc
    * @param id
    * @return the element obtained by the Id, or null if it is not found.
    */
   public static Element getElementById(Document doc, String id) {

      Element result = null;

      result = IdResolver.getElementByIdType(doc, id);

      if (result != null) {
         log.debug(
            "I could find an Element using the simple getElementByIdType method: "
            + result.getTagName());

         return result;
      }

       result = IdResolver.getElementByIdUsingDOM(doc, id);

       if (result != null) {
          log.debug(
             "I could find an Element using the simple getElementByIdUsingDOM method: "
            + result.getTagName());

         return result;
      }
       // this must be done so that Xalan can catch ALL namespaces
       //XMLUtils.circumventBug2650(doc);
      result = IdResolver.getElementBySearching(doc, id);

      if (result != null) {
		  IdResolver.registerElementById(result, id);

         return result;
      }

      return null;
   }
   

    /**
     * Method getElementByIdUsingDOM
     *
     * @param doc
     * @param id
     * @return the element obtained by the Id, or null if it is not found.
     */
    private static Element getElementByIdUsingDOM(Document doc, String id) {
        if (log.isDebugEnabled())
        	log.debug("getElementByIdUsingDOM() Search for ID " + id);
        return doc.getElementById(id);
    }

   /**
    * Method getElementByIdType
    *
    * @param doc
    * @param id
    * @return the element obtained by the Id, or null if it is not found.
    */
   private static Element getElementByIdType(Document doc, String id) {
   	  if (log.isDebugEnabled())
   	  	log.debug("getElementByIdType() Search for ID " + id);
       WeakHashMap elementMap = (WeakHashMap) docMap.get(doc);
       if (elementMap != null) {
           WeakReference weakReference = (WeakReference) elementMap.get(id);
           if (weakReference != null)
           {
                return (Element) weakReference.get();   
           }
       }
       return null;
   }

   
   static java.util.List names;
   static {
	   String namespaces[]={ Constants.SignatureSpecNS,
			   EncryptionConstants.EncryptionSpecNS,
			   "http://schemas.xmlsoap.org/soap/security/2000-12",
			   "http://www.w3.org/2002/03/xkms#"	   	   
		   };
	   names=Arrays.asList(namespaces);
   }
   

   private static Element getElementBySearching(Node root,String id) {
	   Element []els=new Element[4];
	   getElementBySearching(root,id,els);
	   for (int i=0;i<els.length;i++) {
		   if (els[i]!=null) {
			   return els[i];
		   }
	   }
	   return null;
	   
   }
   private static int getElementBySearching(Node root,String id,Element []els) {
	   switch (root.getNodeType()) {
	   case Node.ELEMENT_NODE:
		   Element el=(Element)root;
		   if (el.hasAttributes()) {			  
			   int index=names.indexOf(el.getNamespaceURI());
			   if (index<0) {
				   index=4;
			   }		   
			   if (el.getAttribute("Id").equals(id)) {				   
				   els[index]=el;
				   if (index==0) {
					   return 1;
				   }
			   } else if ( el.getAttribute("id").equals(id) ) {
				   if (index!=2) {
					   index=4;
				   }			    				   
				   els[index]=el;
			   } else if ( el.getAttribute("ID").equals(id) ) {
				   if (index!=3) {
					   index=4;
				   }
				   els[index]=el;				   
			   } else if ((index==3)&&(
				   el.getAttribute("OriginalRequestID").equals(id) ||
				   el.getAttribute("RequestID").equals(id) ||
				   el.getAttribute("ResponseID" ).equals(id))) {
				   els[3]=el;				   
			   }
		   }
	   	case Node.DOCUMENT_NODE:
			Node sibling=root.getFirstChild();
			while (sibling!=null) {
				if (getElementBySearching(sibling,id,els)==1)
					return 1;
				sibling=sibling.getNextSibling();
			}
	   }
	   return 0;
   }

}

@@ -5299,17 +5299,17 @@
 Element%5B
-4
+5
 %5D;%0A%09   g

Fix a bug when, using the insecure id method.