/*
 * Copyright 2003 Federal Chancellery Austria
 * MOA-ID has been developed in a cooperation between BRZ, the Federal
 * Chancellery Austria - ICT staff unit, and Graz University of Technology.
 *
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * http://www.osor.eu/eupl/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */
package at.gv.egovernment.moa.util;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.NodeIterator;
import org.jaxen.JaxenException;
import org.jaxen.NamespaceContext;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.dom.DOMXPath;
import org.jaxen.dom.DocumentNavigator;
/**
 * Utility methods to evaluate XPath expressions on DOM nodes.
 * 
 * @author Patrick Peck
 * @version $Id$
 */
public class XPathUtils {
  /**
   * The XPath expression selecting all nodes under a given root (including the
   * root node itself).
   */
  public static final String ALL_NODES_XPATH =
    "(.//. | .//@* | .//namespace::*)";
  /** The DocumentNavigator to use for navigating the document. */
  private static DocumentNavigator documentNavigator =
    DocumentNavigator.getInstance();
  /** The default namespace prefix to namespace URI mappings. */
  private static NamespaceContext NS_CONTEXT;
  static {
    SimpleNamespaceContext ctx = new SimpleNamespaceContext();
    ctx.addNamespace(Constants.MOA_PREFIX, Constants.MOA_NS_URI);
    ctx.addNamespace(Constants.MOA_CONFIG_PREFIX, Constants.MOA_CONFIG_NS_URI);
    ctx.addNamespace(Constants.MOA_ID_CONFIG_PREFIX, Constants.MOA_ID_CONFIG_NS_URI);
    ctx.addNamespace(Constants.SL10_PREFIX, Constants.SL10_NS_URI);
    ctx.addNamespace(Constants.SL11_PREFIX, Constants.SL11_NS_URI);
    ctx.addNamespace(Constants.SL12_PREFIX, Constants.SL12_NS_URI);
    ctx.addNamespace(Constants.ECDSA_PREFIX, Constants.ECDSA_NS_URI);
    ctx.addNamespace(Constants.PD_PREFIX, Constants.PD_NS_URI);
    ctx.addNamespace(Constants.SAML_PREFIX, Constants.SAML_NS_URI);
    ctx.addNamespace(Constants.SAMLP_PREFIX, Constants.SAMLP_NS_URI);
    ctx.addNamespace(Constants.DSIG_PREFIX, Constants.DSIG_NS_URI);
    ctx.addNamespace(Constants.XSLT_PREFIX, Constants.XSLT_NS_URI);
    ctx.addNamespace(Constants.XSI_PREFIX, Constants.XSI_NS_URI);
    ctx.addNamespace(Constants.DSIG_FILTER2_PREFIX, Constants.DSIG_FILTER2_NS_URI);
    ctx.addNamespace(Constants.DSIG_EC_PREFIX, Constants.DSIG_EC_NS_URI);
    ctx.addNamespace(Constants.MD_PREFIX, Constants.MD_NS_URI);
    ctx.addNamespace(Constants.MDP_PREFIX, Constants.MDP_NS_URI);
    ctx.addNamespace(Constants.MVV_PREFIX, Constants.MVV_NS_URI);
    ctx.addNamespace(Constants.STB_PREFIX, Constants.STB_NS_URI);
    ctx.addNamespace(Constants.WRR_PREFIX, Constants.WRR_NS_URI);
    ctx.addNamespace(Constants.STORK_PREFIX, Constants.STORK_NS_URI);
    ctx.addNamespace(Constants.STORKP_PREFIX, Constants.STORKP_NS_URI);
    ctx.addNamespace(Constants.SAML2_PREFIX, Constants.SAML2_NS_URI);
    ctx.addNamespace(Constants.SAML2P_PREFIX, Constants.SAML2P_NS_URI);
    ctx.addNamespace(Constants.XENC_PREFIX, Constants.XENC_NS_URI);
    ctx.addNamespace(Constants.XADES_1_1_1_NS_PREFIX, Constants.XADES_1_1_1_NS_URI);
    NS_CONTEXT = ctx;
  }
  /**
   * Return a NodeIterator over the nodes matching the XPath
   * expression.
   * 
   * All namespace URIs and prefixes declared in the Constants
   * interface are used for resolving namespaces.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return An iterator over the resulting nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeIterator selectNodeIterator(Node contextNode, String exp)
    throws XPathException {
    return selectNodeIterator(contextNode, NS_CONTEXT, exp);
  }
  /**
   * Return a NodeIterator over the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceElement An element from which to build the
   * namespace mapping for evaluating the XPath expression
   * @param exp The XPath expression to evaluate.
   * @return An iterator over the resulting nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeIterator selectNodeIterator(
    Node contextNode,
    Element namespaceElement,
    String exp)
    throws XPathException {
    try {
      SimpleNamespaceContext ctx = new SimpleNamespaceContext();
      ctx.addElementNamespaces(documentNavigator, namespaceElement);
      return selectNodeIterator(contextNode, ctx, exp);
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Return a NodeIterator over the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceMapping A namespace prefix to namespace URI mapping
   * (String to String) for evaluating the XPath 
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return An iterator over the resulting nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeIterator selectNodeIterator(
    Node contextNode,
    Map namespaceMapping,
    String exp)
    throws XPathException {
    SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
    return selectNodeIterator(contextNode, ctx, exp);
  }
  /**
   * Return a NodeIterator over the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param nsContext The NamespaceContext for resolving namespace
   * prefixes to namespace URIs for evaluating the XPath expression.
   * @param exp The XPath expression to evaluate.
   * @return An iterator over the resulting nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  private static NodeIterator selectNodeIterator(
    Node contextNode,
    NamespaceContext nsContext,
    String exp)
    throws XPathException {
    try {
      DOMXPath xpath = new DOMXPath(exp);
      List nodes;
      xpath.setNamespaceContext(nsContext);
      nodes = xpath.selectNodes(contextNode);
      return new NodeIteratorAdapter(nodes.listIterator());
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Return a NodeList of all the nodes matching the XPath
   * expression.
   * 
   * All namespace URIs and prefixes declared in the Constants
   * interface are used for resolving namespaces.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return A NodeList containing the matching nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeList selectNodeList(Node contextNode, String exp)
    throws XPathException {
    return selectNodeList(contextNode, NS_CONTEXT, exp);
  }
  /**
   * Return a NodeList of all the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceElement An element from which to build the
   * namespace mapping for evaluating the XPath expression
   * @param exp The XPath expression to evaluate.
   * @return A NodeList containing the matching nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeList selectNodeList(
    Node contextNode,
    Element namespaceElement,
    String exp)
    throws XPathException {
    try {
      SimpleNamespaceContext ctx = new SimpleNamespaceContext();
      ctx.addElementNamespaces(documentNavigator, namespaceElement);
      return selectNodeList(contextNode, ctx, exp);
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Return a NodeList of all the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceMapping A namespace prefix to namespace URI mapping
   * (String to String) for evaluating the XPath 
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return A NodeList containing the matching nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static NodeList selectNodeList(
    Node contextNode,
    Map namespaceMapping,
    String exp)
    throws XPathException {
    SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
    return selectNodeList(contextNode, ctx, exp);
  }
  /**
   * Return a NodeList of all the nodes matching the XPath
   * expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param nsContext The NamespaceContext for resolving namespace
   * prefixes to namespace URIs for evaluating the XPath expression.
   * @param exp The XPath expression to evaluate.
   * @return A NodeList containing the matching nodes.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  private static NodeList selectNodeList(
    Node contextNode,
    NamespaceContext nsContext,
    String exp)
    throws XPathException {
    try {
      DOMXPath xpath = new DOMXPath(exp);
      List nodes;
      xpath.setNamespaceContext(nsContext);
      nodes = xpath.selectNodes(contextNode);
      return new NodeListAdapter(nodes);
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Select the first node matching an XPath expression.
   * 
   * All namespace URIs and prefixes declared in the Constants
   * interface are used for resolving namespaces.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return Node The first node matching the XPath expression, or
   * null, if no node matched.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static Node selectSingleNode(Node contextNode, String exp)
    throws XPathException {
    return selectSingleNode(contextNode, NS_CONTEXT, exp);
  }
  /**
   * Select the first node matching an XPath expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceElement An element from which to build the
   * namespace mapping for evaluating the XPath expression
   * @param exp The XPath expression to evaluate.
   * @return Node The first node matching the XPath expression, or
   * null, if no node matched.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static Node selectSingleNode(
    Node contextNode,
    Element namespaceElement,
    String exp)
    throws XPathException {
    try {
      SimpleNamespaceContext ctx = new SimpleNamespaceContext();
      ctx.addElementNamespaces(documentNavigator, namespaceElement);
      return selectSingleNode(contextNode, ctx, exp);
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Select the first node matching an XPath expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param namespaceMapping A namespace prefix to namespace URI mapping
   * (String to String) for evaluating the XPath 
   * expression.
   * @param exp The XPath expression to evaluate.
   * @return Node The first node matching the XPath expression, or
   * null, if no node matched.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static Node selectSingleNode(
    Node contextNode,
    Map namespaceMapping,
    String exp)
    throws XPathException {
    SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
    return selectSingleNode(contextNode, ctx, exp);
  }
  /**
   * Select the first node matching an XPath expression.
   * 
   * @param contextNode The root node from which to evaluate the XPath
   * expression.
   * @param nsContext The NamespaceContext for resolving namespace
   * prefixes to namespace URIs for evaluating the XPath expression.
   * @param exp The XPath expression to evaluate.
   * @return Node The first node matching the XPath expression, or
   * null, if no node matched.
   * @throws XPathException An error occurred evaluating the XPath expression.
   */
  public static Node selectSingleNode(
    Node contextNode,
    NamespaceContext nsContext,
    String exp)
    throws XPathException {
    try {
      DOMXPath xpath = new DOMXPath(exp);
      xpath.setNamespaceContext(nsContext);
      return (Node) xpath.selectSingleNode(contextNode);
    } catch (JaxenException e) {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { exp });
      throw new XPathException(message, e);
    }
  }
  /**
   * Return the value of a DOM element whose location is given by an XPath
   * expression.
   * 
   * @param root The root element from which to evaluate the XPath.
   * @param xpath The XPath expression pointing to the element whose value
   * to return.
   * @param def The default value to return, if no element can be found using
   * the given xpath.
   * @return The element value, if it can be located using the
   * xpath. Otherwise, def is returned.
   */
  public static String getElementValue(
    Element root,
    String xpath,
    String def) {
    Element elem = (Element) XPathUtils.selectSingleNode(root, xpath);
    return elem != null ? DOMUtils.getText(elem) : def;
  }
  /**
   * Return the value of a DOM attribute whose location is given by an XPath
   * expression.
   * 
   * @param root The root element from which to evaluate the XPath.
   * @param xpath The XPath expression pointing to the attribute whose value to
   * return.
   * @param def The default value to return, if no attribute can be found using
   * the given xpath.
   * @return The element value, if it can be located using the
   * xpath. Otherwise, def is returned.
   */
  public static String getAttributeValue(
    Element root,
    String xpath,
    String def) {
    Attr attr = (Attr) XPathUtils.selectSingleNode(root, xpath);
    return attr != null ? attr.getValue() : def;
  }
  
  /**
   * Returns the namespace prefix used within XPathUtils for referring to
   * the namespace of the specified (Security Layer command) element.
   * 
   * This namespace prefix can be used in various XPath expression evaluation methods 
   * within  XPathUtils without explicitely binding it to the particular
   * namespace.
   * 
   * @param contextElement The (Security Layer command) element. 
   *            
   * @return  the namespace prefix used within XPathUtils for referring to
   *          the namespace of the specified (Security Layer command) element.
   * 
   * throws XpathException If the specified element has a namespace other than the ones
   *        known by this implementation as valid Security Layer namespaces (cf. 
   *        @link Constants#SL10_NS_URI, @link Constants#SL11_NS_URI, @link Constants#SL12_NS_URI).
   */
  public static String getSlPrefix (Element contextElement) throws XPathException 
  {
    String sLNamespace = contextElement.getNamespaceURI();
    String sLPrefix = null;
    if (sLNamespace.equals(Constants.SL10_NS_URI)) 
    {
      sLPrefix = Constants.SL10_PREFIX;
    }  
    else if (sLNamespace.equals(Constants.SL12_NS_URI)) 
    {
      sLPrefix = Constants.SL12_PREFIX;
    }
    else if (sLNamespace.equals(Constants.SL11_NS_URI)) 
    {
      sLPrefix = Constants.SL11_PREFIX;
    } 
    else 
    {
      MessageProvider msg = MessageProvider.getInstance();
      String message = msg.getMessage("xpath.00", new Object[] { "Ung�ltiger Security Layer Namespace: \"" + sLNamespace + "\"."});
      throw new XPathException(message, null);
    }
    
    return sLPrefix;
  }
  
  
  /**
   * Return the SecurityLayer namespace prefix of the context element.
   * If the context element is not the element that lies within the 
   * SecurityLayer namespace. The Securitylayer namespace is derived from
   * the xmlns:sl10, sl11 or sl 
   * attribute of the context element.
   * 
   * The returned prefix is needed for evaluating XPATH expressions.
   * 
   * @param contextElement The element to get a prefix for the Securitylayer namespace,
   *                       that is used within the corresponding document. 
   *            
   * @return  The string sl10, sl11 or sl,
   *          depending on the SecurityLayer namespace of the contextElement.
   * 
   * throws XPathException If no (vlalid) SecurityLayer namespace prefix or namespace
   *                       is defined.
   */
  public static String getSlPrefixFromNoRoot (Element contextElement) throws XPathException {
    
    String slPrefix = checkSLnsDeclaration(contextElement, Constants.SL10_PREFIX, Constants.SL10_NS_URI);
    if (slPrefix == null) {
      slPrefix = checkSLnsDeclaration(contextElement, Constants.SL11_PREFIX, Constants.SL11_NS_URI);
    }
    if (slPrefix == null) {
      slPrefix = checkSLnsDeclaration(contextElement, Constants.SL12_PREFIX, Constants.SL12_NS_URI);
    }       
     
    return slPrefix;
       
  }
  
  /**
   * Checks if the context element has an attribute xmlns:slPrefix and
   * if the prefix of that attribute corresponds with a valid SecurityLayer namespace.
   * 
   * @param contextElement  The element to be checked.
   * @param slPrefix        The prefix which should be checked. Must be a valid SecurityLayer
   *                        namespace prefix.
   * @param slNameSpace     The SecurityLayer namespace that corresponds to the specified prefix.
   *  
   * @return                The valid SecurityLayer prefix or null if this prefix is
   *                        not used.
   * @throws XPathException
   */
  private static String checkSLnsDeclaration(Element contextElement, String slPrefix, String slNameSpace)
      throws XPathException 
  {
    String nsAtt = "xmlns:" + slPrefix;
    String nameSpace = contextElement.getAttribute(nsAtt);
    if (nameSpace == "") {
      return null;
    } else {
      // check if namespace is correct
      if (nameSpace.equals(slNameSpace)) {
        return slPrefix;
      } else {
        MessageProvider msg = MessageProvider.getInstance();
        String message = msg.getMessage("xpath.00", new Object[] { "Ung�ltiger SecurityLayer Namespace: \"" + nameSpace + "\"."});
        throw new XPathException(message, null);
      }
    }
  }
}