content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.faban.harness;
  
  import com.sun.faban.common.NameValuePair;
  import com.sun.faban.harness.util.XMLReader;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.StringTokenizer;
  import java.util.Collections;
  
  /**
   * The ParamRepository is the programmatic representation of the
   * configuration file. It allows access to the xml file via xpath.
   * In addition, the ParamRepository also allows updating the configuration
   * file. Such updates should be made during the validation stage.
   */
  public class ParamRepository {
  
  
      private XMLReader reader;
      private List<HostConfig> hostConfigs;
  
      /**
       * Constructor: Open specified repository.
       * @param file Name of repository
       * @param warnDeprecated Log warning when config file is deprecated
       */
      public ParamRepository(String file, boolean warnDeprecated) {
          reader = new XMLReader(file, true, warnDeprecated);
          reader.processHostPorts(); //Pre-scan the hosts:ports fields
      }
  
      /**
       * Generic parameter access method.
       * @param xpath
       * @return value of the parameter of type string
       */
      public String getParameter(String xpath) {
          return reader.getValue(xpath);
      }
  
      /**
       * Generic parameter access method.
       * @param xpath string
       * @param top element
       * @return value of the parameter
       */
      public String getParameter(String xpath, Element top) {
          return reader.getValue(xpath, top);
      }
  
      /**
       * Generic method to get NodeList for a given tagName.
       * @param tagName of type string
       * @return NodeList for the tagName
       */
      public NodeList getNodeListForTagName(String tagName){
          return reader.getNodeListForTagName(tagName);
      }
  
      /**
       * Generic method to get a list of top level nodes.
       * @return NodeList
       */
      public NodeList getTopLevelElements() {
          NodeList topLevelElements = reader.getTopLevelElements();
          return topLevelElements;
      }
  
       /**
       * Generic method to get NodeList for a given xpath.
       * @param xPath string
       * @return NodeList for the xpath
       */
      public NodeList getNodes(String xPath) {
          return reader.getNodes(xPath);
      }
  
      /**
       * Generic method to get NodeList for a given xpath
       * with respect to base node.
       * @param xPath string
       * @param top element
       * @return NodeList
       */
      public NodeList getNodes(String xPath, Element top) {
          return reader.getNodes(xPath, top);
      }
  
      /**
       * Generic method to get a Node for a given xpath.
       * @param xPath string
       * @return Node for the xpath
       */
      public Node getNode(String xPath) {
          return reader.getNode(xPath);
      }
  
      /**
       * Generic method to get a Node for a given xpath
       * with respect to base node.
       * @param xPath string
       * @param top element
       * @return Node for the xpath
       */
      public Node getNode(String xPath, Element top) {
          return reader.getNode(xPath, top);
      }
  
      /**
       * Adds a new XPath to the param repository.
       * @param baseXPath The base XPath to add this node
       * @param paramName The element name
       * @return The newly added element, or null on failure
       */
      public Element addParameter(String baseXPath, String paramName) {
          return reader.addNode(baseXPath, null, null, paramName);
      }
  
      /**
       * Adds a new XPath to the param repository.
       * @param baseXPath The base XPath to add this node
       * @param namespace The namespace for this new element, if any
       * @param prefix The namespace prefix for this new element, if any
       * @param paramName The element name
       * @return The newly added element, or null on failure
       */
      public Element addParameter(String baseXPath, String namespace,
                                  String prefix, String paramName) {
          return reader.addNode(baseXPath, namespace, prefix, paramName);
      }
  
      /**
       * Adds a new XPath to the param repository,
       * based on a previously added parameter.
       * @param parent The parent element to add this node to
       * @param paramName The element name
       * @return The newly added element, or null on failure
       */
      public Element addParameter(Element parent, String paramName) {
          return reader.addNode(parent, null, null, paramName);
      }
  
      /**
       * Adds a new XPath to the param repository,
       * based on a previously added parameter.
       * @param parent The parent element to add this node to
       * @param namespace The namespace for this new element, if any
       * @param prefix The namespace prefix for this new element, if any
       * @param paramName The element name
       * @return The newly added element, or null on failure
       */
      public Element addParameter(Element parent, String namespace,
                                  String prefix, String paramName) {
          return reader.addNode(parent, namespace, prefix, paramName);
      }
  
      /**
       * Sets or replaces the parameter referenced by the XPath.
       * @param xpath The xpath referencing the parameter
       * @param newValue The new value to set
       */
      public void setParameter(String xpath, String newValue) {
          reader.setValue(xpath, newValue);
      }
  
      /**
       * Sets the parameter for a newly added DOM element.
       * @param element The newly added element
       * @param value The value to assign to the element
       */
      public void setParameter(Element element, String value) {
          reader.setValue(element, value);
      }
  
      /**
       * Saves the parameter repository back to file if it has been modified.
       *
       * @throws Exception If there is an exception saving the repository.
       */
      public void save() throws Exception {
          reader.save(null);
      }
  
      /**
       * Generic parameter access method.
       * @param xpath
       * @return list containing all paramters with the xpath
       */
      public List<String> getParameters(String xpath) {
          return reader.getValues(xpath);
      }
  
      /**
       * Gets the attribute values for the specified attribute of a certain XPath.
       *
       * @param elementPath The XPath of the element
       * @param attributeName The name of the attribute
       * @return A list of attribute values
       */
      public List<String> getAttributeValues(String elementPath, String attributeName) {
          return reader.getAttributeValues(elementPath, attributeName);
      }
  
      /**
       * HostConfig structure holding a hostConfig element from the config file.
       */
      public static class HostConfig {
          public String[] hosts;
          public int[] numCpus;
          public String tools;
          public String userCommands;
      }
  
      private void readHostConfigs() throws ConfigurationException {
          ArrayList<HostConfig> hostConfigs = new ArrayList<HostConfig>();
          NodeList topLevelElements = getTopLevelElements();
          int topLevelSize = topLevelElements.getLength();
          for (int i = 0; i < topLevelSize; i++) {
              Node node = topLevelElements.item(i);
              if (node.getNodeType() != Node.ELEMENT_NODE) {
                  continue;
              }
              Element ti = (Element) node;
              String ns = ti.getNamespaceURI();
              String topElement = ti.getNodeName();
  
              if("http://faban.sunsource.net/ns/fabanharness".equals(ns) &&
                      "jvmConfig".equals(topElement))
                  continue;
  
              // Get the hosts
              String[] hosts = getEnabledHosts(ti);
              if (hosts == null || hosts.length == 0)
                  continue;
  
              HostConfig hostConfig = new HostConfig();
              hostConfig.hosts = hosts;
              String[] vals = getTokenizedValue("fa:hostConfig/fh:cpus", ti);
              if (vals != null) {
                  hostConfig.numCpus = new int[vals.length];
                  for (int j = 0; j < vals.length; j++) {
                      try {
                          hostConfig.numCpus[j] = Integer.parseInt(vals[j]);
                      } catch (NumberFormatException e) {
                          throw new ConfigurationException(
                                  "fa:hostConfig/fh:cpus under " +
                                  node.getNodeName() +
                                  " has a non-integer value: " + vals[j] + '.',
                                  e);
                      }
                  }
              }
  
              hostConfig.tools = getParameter("fa:hostConfig/fh:tools", ti);
              if (hostConfig.tools != null)
                  hostConfig.tools = hostConfig.tools.trim();
              if (hostConfig.tools.length() == 0)
                  hostConfig.tools = null;
  
              hostConfig.userCommands =
                      getParameter("fa:hostConfig/fh:userCommands", ti);
              if (hostConfig.userCommands != null)
                  hostConfig.userCommands = hostConfig.userCommands.trim();
              if (hostConfig.userCommands.length() == 0)
                  hostConfig.userCommands = null;
  
              hostConfigs.add(hostConfig);
          }
          this.hostConfigs = Collections.unmodifiableList(hostConfigs);
      }
  
      /**
       * Obtains the list of enabled host configurations.
       * @return A list of enabled hosts configurations
       * @throws ConfigurationException Error reading the configuration
       */
      public List<HostConfig> getHostConfigs() throws ConfigurationException {
          if (hostConfigs == null)
              readHostConfigs();
          return hostConfigs;
      }
  
      /**
       * Obtains the list of enabled hosts.
       * @param base element
       * @return A list of enabled hosts, grouped by host type.
       * @throws ConfigurationException
       */
      public String[] getEnabledHosts(Element base) throws ConfigurationException {
          String[] enabledHosts;
          if (getBooleanValue("fa:hostConfig/fh:enabled", base, true)) {
              // TODO: Take care of managed hosts/ports
              enabledHosts = getTokenizedValue("fa:hostConfig/fh:managedHosts",
                                                  base);
              if (enabledHosts == null || enabledHosts.length == 0)
                  enabledHosts = getTokenizedValue("fa:hostConfig/fa:host", base);
  
              if (enabledHosts == null)
                  enabledHosts = new String[0];
          } else {
              enabledHosts = new String[0];
          }
         return enabledHosts;
      }
  
      /**
       * Obtains the list of enabled hostports.
       * @param base element
       * @return A list of enabled hostports.
       * @throws ConfigurationException
       */
      public List<NameValuePair<Integer>> getEnabledHostPorts(Element base)
              throws ConfigurationException {
          if (getBooleanValue("fa:hostConfig/fh:enabled", base))
              return getHostPorts(base);
          else
              return null;
      }
  
      /**
       * This returns tokenized values of parameters in a list.
       * Mainly used to get host(s)
       * @param xpath The xpath to the parameters
       * @return List of tokenized values
       */
      public List<String[]> getTokenizedParameters(String xpath) {
          ArrayList<String[]> params = new ArrayList<String[]>();
          List<String> entries = reader.getValues(xpath);
          for (String entry : entries) {
              StringTokenizer st = new StringTokenizer(entry);
              String[] values = new String[st.countTokens()];
              for (int i = 0; st.hasMoreTokens(); i++)
                  values[i] = st.nextToken();
              params.add(values);
          }
          return params;
      }
  
      /**
       * Obtains the value at an XPath, tokenized into an array.
       * @param xpath XPath expression to get SPACE seperated values from a single
       * parameter. For Example sutConfig/host The values are seperated by SPACE
       * @return An array of hostnames.
       */
      public String[] getTokenizedValue(String xpath) {
          String val = reader.getValue(xpath);
          String[] hosts = null;
          if (val != null) {
              val = val.trim();
              if (val.length() > 0) {
                  StringTokenizer st = new StringTokenizer(val);
                  hosts = new String[st.countTokens()];
                  for (int i = 0; st.hasMoreTokens(); i++)
                      hosts[i] = st.nextToken();
              }
          }
  
          if (hosts == null)
              hosts = new String[0];
          return hosts;
      }
  
      /**
       * Obtains the value at an XPath, tokenized into an array, from a specific
       * base node in the document.
       * @param xpath XPath expression to get SPACE seperated values from a single
       * parameter. For Example sutConfig/host The values are seperated by SPACE
       * @param base The base element.
       * @return An array of hostnames.
       */
      public String[] getTokenizedValue(String xpath, Element base) {
          String value = reader.getValue(xpath, base);
          if (value == null)
              return null;
          value = value.trim();
          if (value.length() == 0)
              return null;
          StringTokenizer st = new StringTokenizer(value);
          String[] hosts = new String[st.countTokens()];
          for (int i = 0; st.hasMoreTokens(); i++)
              hosts[i] = st.nextToken();
          return hosts;
      }
  
      /**
       * Obtains xpath values matching an xpath, broken into tokens.
       * @param xpath XPath expression to get  ',' and SPACE seperated 
       * values from a single parameter. For Example sutConfig/instances
       * The values are seperated by ',' and then by SPACE
       * @return List of arrays of hostnames
       */
      public List<String[]> getTokenizedList(String xpath) {
          // Each value should be passed as , and SPACE seperated strings
          ArrayList<String[]> list = new ArrayList<String[]>();
          StringTokenizer st = new StringTokenizer(reader.getValue(xpath));
          while (st.hasMoreTokens()) {
              ArrayList<String> l = new ArrayList<String>();
              StringTokenizer st2 = new  StringTokenizer(st.nextToken(), ",");
              while (st2.hasMoreTokens())
                  l.add(st2.nextToken());
  
              list.add(l.toArray(new String[1]));
          }
          return list;
      }
  
      /**
       * Obtains the host:port name value pair list from the element
       * matching this XPath.
       * @param xPathExpr
       * @return The list of host:port elements, or null if the XPath does
       * not exist or does not point to a host:port node.
       */
      public List<NameValuePair<Integer>> getHostPorts(String xPathExpr) {
          return reader.getHostPorts(xPathExpr);
      }
  
      /**
       * Obtains the host:port pairs at this element.
       * @param base The base element
       * @return The list of host:port pairs
       */
      public List<NameValuePair<Integer>> getHostPorts(Element base) {
          return reader.getHostPorts(base);
      }
  
      /**
       * Obtains the host/role mappings.
       * @return The host/role mappings
       * @throws ConfigurationException Invalid host/role mapping
       */
      public List<NameValuePair<String>> getHostRoles()
              throws ConfigurationException {
  
          ArrayList<NameValuePair<String>> hostTypeList =
                  new ArrayList<NameValuePair<String>>();
          NodeList topLevelElements = getTopLevelElements();
          int topLevelSize = topLevelElements.getLength();
          for (int i = 0; i < topLevelSize; i++) {
              Node node = topLevelElements.item(i);
              if (node.getNodeType() != Node.ELEMENT_NODE) {
                  continue;
              }
              Element ti = (Element) node;
              String ns = ti.getNamespaceURI();
              String topElement = ti.getNodeName();
              if ("http://faban.sunsource.net/ns/fabanharness".equals(ns) &&
                      "jvmConfig".equals(topElement))
                  continue;
  
              // Get the hosts
              String[] hosts = getEnabledHosts(ti);
              if (hosts == null || hosts.length == 0)
                  continue;
  
              // Get the type of that host. This is the top level element name.
              String type = ti.getNodeName();
  
              // Then add the host and type pair to the list.
              for (String host : hosts) {
                  NameValuePair<String> hostType = new NameValuePair<String>();
                  hostType.name = host;
                  hostType.value = type;
                  hostTypeList.add(hostType);
              }
          }
          return hostTypeList;
      }
  
      /**
       * This method reads a value using the XPath and converts it to a boolean.
       * @param xpath XPath expression to the value which is true or false
       * @return true or false
       */
      public boolean getBooleanValue(String xpath) {
          return  Boolean.valueOf(reader.getValue(xpath)).booleanValue();
      }
  
      /**
       * Obtains the boolean value at the given xpath, providing a default
       * value if it does not exist or is not a boolean.
       * @param xpath The given xpath
       * @param defaultValue The default value
       * @return The boolean at the xpath, or the given default
       */
      public boolean getBooleanValue(String xpath, boolean defaultValue) {
          String s = reader.getValue(xpath);
          if (s == null || s.length() == 0)
              return defaultValue;
          else
              return Boolean.parseBoolean(s);
  
      }
  
      /**
       * This method reads a value using the XPath and converts it to a boolean.
       * @param xpath XPath expression to the value which is true or false
       * @param base element
       * @return true or false
       */
      public boolean getBooleanValue(String xpath, Element base) {
          return  Boolean.valueOf(reader.getValue(xpath, base)).booleanValue();
      }
  
      /**
       * This method reads a value using the XPath and converts it to a boolean.
       * @param xpath XPath expression to the value which is true or false
       * @param base element
       * @param defaultValue
       * @return true or false
       */
      public boolean getBooleanValue(String xpath, Element base,
                                     boolean defaultValue) {
          String s = reader.getValue(xpath, base);
          if (s == null || s.length() == 0)
              return defaultValue;
          else
              return Boolean.parseBoolean(s);
  
      }
  }
  
  