/* Copyright (c) 2002, Oracle Corporation. All Rights Reserved. */
package oracle.portal.sample.portalTools.devguide.jcasample;

import java.net.URL;
import java.net.MalformedURLException;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.Record;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.ResourceWarning;
import javax.resource.cci.Connection;
import javax.resource.ResourceException;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;

import oracle.xml.parser.v2.DOMParser;
import oracle.xml.parser.v2.XMLParseException;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.parser.v2.XMLNode;
import oracle.xml.parser.v2.XMLElement;


/** 
 * This class implements JCA <code>Interaction</code> interface for the sample 
 * adapter.
 *
 * This simple interaction will get the method name to be called and the  
 * maximum number of output rows to return from the interaction spec. This  
 * sample interaction supports two methods.
 * <ul>
 *  <li> getEmployee: Gets an employee number from the input record and returns
 *       a single row for that employee record. The input record must define
 *       a record element named "EmployeeNumber".
 *  <li> getEmployeesUnderManager: Gets employee id for the manager and
 *       returns rows for the employees who are under that manager. The input 
 *       record must define a record element named "ManagerEmployeeNumber".
 * </ul>  
 *
 */
public class SampleInteraction implements Interaction
{
  private Connection mConnection = null;
  private String mDataLoc = null;

  // Supported methods
  private static final String M_GET_EMPLOYEE = "getEmployee";
  private static final String M_GET_EMPLOYEES_UNDER_MANAGER = "getEmployeesUnderManager";
  

  /**
   * Create an interaction object for the JCA adapter.
   * @param conn Connection object that creates this object.
   * @param dataLoc Location of the data.
   */
  protected SampleInteraction(Connection conn, String dataLoc)
  {
    mConnection = conn;
    mDataLoc = dataLoc;
  }

  /** 
   * Get the connection objct. 
   * @return Connection object associated with this object.
   */
  public Connection getConnection()
  {
    return mConnection;
  }

  /** 
   * Execute the interaction.
   *
   * @param spec Interaction spec, if defined in the provider definition under 
   *             "interactionHandler" tag.
   * @param input Input record in a form of MappedRecord, created from the 
   *              "recordElement"s defined under "inputRecordHandler" tag.
   * @return This methos returns a ResultSet.
   * @throws ResourceException
   */
  public Record execute(InteractionSpec spec, Record input) 
    throws ResourceException
  {
    try
    {
      SampleResultSet res = null;

      // Check the interaction spec to get the method name
      String methodName = ((SampleInteractionSpec)spec).getMethodName();
      int maxRow = ((SampleInteractionSpec)spec).getMaxRow();

      // Connect to the data source
      URL url = createURL(mDataLoc);      

      // Now load the XML data file as a DOM tree
      DOMParser dp = new DOMParser();
      dp.setPreserveWhitespace(false);
      dp.parse(url.openStream());
      Element root = dp.getDocument().getDocumentElement();

      // Input record is always a MappedRecord
      MappedRecord inRec = (MappedRecord) input;

      // Call the method
      if (methodName.equals(M_GET_EMPLOYEE))
      {
        // Get the employee id set from the input record. 
        String empId = (String) inRec.get("EmployeeNumber");
        res = getEmployee(root, empId);
      }
      else if(methodName.equals(M_GET_EMPLOYEES_UNDER_MANAGER))
      {
        // Get the employee id for the manager set from the input record. 
        String empId = (String) inRec.get("ManagerEmployeeNumber");
        res = getEmployeesUnderManager(root, empId, maxRow);
      }

      return res;
    }
    catch(Exception ex)
    {
      ResourceException rex = new ResourceException(ex.toString());
      rex.setLinkedException(ex);
      throw rex;
    }
  }

  /** 
   * Execute the interaction.
   *
   * @param spec Interaction spec, if defined in the provider definition under 
   *             "interactionHandler" tag.
   * @param input Input record in a form of MappedRecord, created from the 
   *              "recordElement"s defined under "inputRecordHandler" tag.
   * @param output The result in a form of ResultSet.
   * @return True if successful to create the result.
   * @throws ResourceException
   */
  public boolean execute(InteractionSpec spec, Record input, Record output) 
    throws ResourceException
  {
    try
    {
      output = execute(spec, input);
      return true;
    }
    catch(Exception ex)
    {
      ResourceException rex = new ResourceException(ex.toString());
      rex.setLinkedException(ex);
      throw rex;
    }
  }

  /** Close the interaction. We will do nothing here. */
  public void close() throws ResourceException
  {
  }

  /** We will do nothing and return a null always. */
  public ResourceWarning getWarnings() throws ResourceException
  {
    return null;
  }

  /** We will do nothing here.*/
  public void clearWarnings() throws ResourceException
  {
  }

  ////////////////////////// Class Helper Methods /////////////////////////////
  /**
   * Get the employees for an employee number.
   *
   * @param el Root element of the XML data stream, holds the collection of the 
   *           rows.
   * @param empNo Employee number to match.
   * @exception Exception If fails to execute.
   */
  private SampleResultSet getEmployee(Element el, String empNo)
    throws Exception
  {
    SampleResultSet res = new SampleResultSet();
    NodeList rows = el.getChildNodes();
    int cnt = rows.getLength();
    for (int i = 0; i < cnt; i++)
    {
      // Get the row
      Element elRow = (Element) rows.item(i);
      //
      // Now check if the given filter attributes, supplied through the input
      // record, are satisfied for this row. If they matched, we wll add this 
      // row data to the result set.
      //
      if (isMatched(elRow, "EMPLOYEE_ID", empNo))
      {
        res.addRow(createRow(elRow));
        return res;
      }
    }
    
    return res;
  }

  /**
   * Get the employees under a manger.
   *
   * @param el Root element of the XML data stream, holds the collection of the 
   *           rows.
   * @param mgrEmpNo Employee number of the manager.
   * @param mxRow    Maximum nuber of rows the result set will hold.
   * @exception Exception If fails to execute.
   */
  private SampleResultSet getEmployeesUnderManager(
    Element el, 
    String mgrEmpNo,
    int maxRow)
  throws Exception
  {
    SampleResultSet res = new SampleResultSet();
    NodeList rows = el.getChildNodes();
    int cnt = rows.getLength();
    int nRowAdded = 0;
    for (int i = 0; i < cnt; i++)
    {
      if (nRowAdded >= maxRow)
        break;
        
      // Get the row
      Element elRow = (Element) rows.item(i);
      //
      // Now check if the given filter attributes, supplied through the input
      // record, are satisfied for this row. If they matched, we wll add this 
      // row data to the result set.
      //
      if (isMatched(elRow, "MANAGER_ID", mgrEmpNo))
      {
        res.addRow(createRow(elRow));
        nRowAdded++;
      }
    }
    
    return res;
  }


  /**
   * Create the URL from the location, if it's file location make sure to
   * add the protocol prefix.
   * @param dataLocation Location of the URL to be created.
   * @exception  if fails to create the URL.
   */
  private URL createURL(String dataLocation) throws Exception
  {
    URL url = null;
    
    try
    {
      url = new URL(dataLocation);
    }
    catch (MalformedURLException ex)
    {
      File f = new File(dataLocation);
      String path = f.getAbsolutePath();
      String fs = System.getProperty("file.separator");
      if (fs.length() == 1)
      {
        char sep = fs.charAt(0);
        if (sep != '/')
          path = path.replace(sep, '/');
        if (path.charAt(0) != '/')
          path = '/' + path;
      }
      path = "file://" + path;
      url = new URL(path);
    }

    return url;
  }

  /**
   * Check if the input data value matched the XML element. 
   * 
   * This method will check the element if it has a child
   * tag with name matches the filterName. If no such tag is present,
   * the element is selected. If the tag is present, it compares
   * the tag value with filterVal. If the value matches, this
   * method returns true. Else returns false.
   *
   * @param elRow  Element as a row, to be matched against the filter.
   * @param filterName Name of the filter. Name to search a sub tag of the element, if present.
   * @param filterValue Value of the filter to be matched with the tag value.
   * @throws Exception
   */
  private boolean isMatched(
    Element elRow, 
    String filterName, 
    String filterVal)
  throws Exception
  {

    // If no value to match, we will select the row
    if (null == filterVal)
      return true;

    //
    // Look for the name as a tag under the row element. If the name is not 
    // found defined under the elRow, we will select the row. If found, we will 
    // match the value to the value of the tag. If it doesn't match we will 
    // return false.
    //
      
    NodeList nl = elRow.getElementsByTagName(filterName);
    if ((null != nl) && (nl.getLength() > 0))
    {
      // Get the first item
      Element elCol = (Element) nl.item(0);
      String val = elCol.getFirstChild().getNodeValue();
      if ((null == val) || !filterVal.equals(val))
        return false;
    }

    return true;
  }

  /**
   * Create a row from the XML element. The row is created as a Map object. 
   */
  private Map createRow(Element elRow) throws Exception 
  {
    Map row = new HashMap(5);
    NodeList children = elRow.getChildNodes();
    int cnt = children.getLength();

    for (int i = 0; i < cnt; i++)
    {
      Element el = (Element) children.item(i);
      Node child = el.getFirstChild();
      if (null != child)
        row.put(el.getNodeName(), el.getFirstChild().getNodeValue());
    }
    return row;
  }
  
}
