/*
 * BILinkedQueryListener.java
 *
 * Copyright © 1994-2004, Oracle. All rights reserved. Oracle grants
 * you ("Licensee") a non-exclusive, royalty-free, worldwide license to use,
 * modify and redistribute this software in source and binary code form, provided
 * that (i) this copyright notice and license appear on all copies of the
 * software; (ii) Licensee does not utilize the software in a manner disparaging
 * to Oracle; and (iii) Licensee does not utilize the software in a manner that
 * violates the terms of any Licensee agreement with Oracle.
 */
package samples.javaclient;

import java.util.Enumeration;
import java.util.Vector;

import oracle.dss.dataSource.client.QueryClient;

import oracle.dss.dataSource.common.DrillRequestedEvent;
import oracle.dss.dataSource.common.LayoutChangedEvent;
import oracle.dss.dataSource.common.Query;
import oracle.dss.dataSource.common.QueryAdapter;
import oracle.dss.dataSource.common.QueryException;
import oracle.dss.dataSource.common.SelectionChangedEvent;

import oracle.dss.datautil.QueryAccess;

import oracle.dss.dataView.Dataview;

import oracle.dss.metadataManager.common.MetadataManagerException;

import oracle.dss.selection.Selection;
import oracle.dss.selection.SelectionException;
import oracle.dss.selection.step.InvalidStepArgException;

import oracle.dss.util.DataAccess;
import oracle.dss.util.DataAvailableEvent;
import oracle.dss.util.DataChangedEvent;
import oracle.dss.util.DataDirector;
import oracle.dss.util.DataDirectorException;
import oracle.dss.util.DataDirectorListener;
import oracle.dss.util.EdgeOutOfRangeException;
import oracle.dss.util.LayerOutOfRangeException;
import oracle.dss.util.MetadataMap;
import oracle.dss.util.QDR;
import oracle.dss.util.QDRMember;
import oracle.dss.util.SliceOutOfRangeException;
import oracle.dss.util.WaitDataAvailableEvent;

/**
 * This class is designed to listen for Query events.  In particular, we are
 * interested in tracking events that have an effect on our linked views.
 *
 * @see oracle.dss.dataSource.common.QueryAdapter
 * @see oracle.dss.util.DataDirectorListener
 */


public class BILinkedQueryListener extends QueryAdapter
  implements DataDirectorListener {

  /////////////////////
  //
  // Constants
  //
  /////////////////////

  /////////////////////
  //
  // Member Variables
  //
  /////////////////////

  // Data Access object
  protected DataAccess m_dataAccess = null;

  // Data Director object
  protected DataDirector m_dataDirector = null;

  // Data source object
  protected Query m_query   = null;

  // LinkedViewPanel which contains linked views of interest
  protected BILinkedViewPanel m_linkedViewPanel = null;

  // Drill flag used to insure that we don't end up in an infinite loop while
  // processing linked drill operations
  protected boolean m_bInDrill = false;

  // Pivot flag used to insure that we don't end up in an infinite loop while
  // processing linked drill operations
  protected boolean m_bInPivot = false;

  // Paging flag used to insure that we don't end up in an infinite loop while
  // processing linked page operations
  protected boolean m_bInPaging = false;

  // Selection changing flag used to insure that we don't end up in an infinite
  // loop while processing linked selection operations
  protected boolean m_bInSelectionChanging = false;

  /////////////////////
  //
  // Constructors
  //
  /////////////////////

  /**
   * Constructor for <code>BILinkedQueryListener</code>.
   *
   * @param query a <code>Query</code> value associated with the
   *        <code>BILinkedQueryListener</code>.
   *
   */
  public BILinkedQueryListener(Query query) {
    super();

    // Remember the query
    m_query = query;

    // Set up the query listeners
    setDataDirector(m_query.createCubeDataDirector());
    getDataDirector().addDataDirectorListener(this);
  }

  /**
   * Constructor for <code>BILinkedQueryListener</code>.
   *
   * @param query a <code>Query</code> value associated with the
   *        <code>BILinkedQueryListener</code>.
   * @param linkViewPanel a <code>LinkedViewPanel</code> value associated with
   *        the <code>BILinkedQueryListener</code>.
   *
   */
  public BILinkedQueryListener(Query ds, BILinkedViewPanel linkedViewPanel) {
    super();

    // Remember the query and LinkedViewPanel
    m_query = ds;
    m_linkedViewPanel = linkedViewPanel;

    // Set up the query listeners
    setDataDirector(m_query.createCubeDataDirector());
    getDataDirector().addDataDirectorListener(this);
  }

  /////////////////////
  //
  // Public Methods
  //
  /////////////////////

  /**
   * Removes the <code>DataDirectorListener</code> listeners associated with
   * the <code>BILinkedQueryListener</code>.
   *
   */
  public void removeDataDirectorListener() {
    getDataDirector().removeDataDirectorListener(this);
  }

  /**
   * Called when a drill will be executed.  This event is fired
   * before the drill operation executes.
   *
   * This event may not be consumed.
   *
   * @param drillRequestedEvent a <code>DrillRequestedEvent</code> value
   *        that contains information about the drill.
   */
  public void drillRequested(DrillRequestedEvent drillRequestedEvent) {
    // Check to see if we have any linked view panels
    BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
    if (linkedViewPanel == null) {
      return;
    }

    // Determine the relationships between the panels
    int nRelationship = linkedViewPanel.getRelationship();

    // Only drill if we have linked selections or are in Master/Detail
    // relationship
    if ((linkedViewPanel.getDimensions() != null) ||
        (nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL)) {
      Query query = (Query)drillRequestedEvent.getSource();

      // Retrieve the master dataview
      Dataview dataviewMaster = linkedViewPanel.getDataviewMaster();
      if (dataviewMaster == null) {
        return;
      }

      if (((nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL) &&
           (dataviewMaster.getDataSource() == query)) ||
           (nRelationship == BILinkedViewPanel.RELATIONSHIP_BIDIRECTIONAL)) {

        try {
          // Check the drill flag to insure that we don't end up in a recursive
          // loop where we get drillRequested events in the middle of a drill
          if (!m_bInDrill) {
            m_bInDrill = true;

            // Determine which views need to be drilled
            Vector vDataviews = null;
            if (nRelationship == BILinkedViewPanel.RELATIONSHIP_BIDIRECTIONAL) {
              // If we are bi-directional, apply the drill to all other views
              vDataviews = linkedViewPanel.getDataviews(query);
            }
            else {
              // If we are master/detail, apply the drill to all other detail
              // views BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL
              vDataviews = linkedViewPanel.getDataviewsDetail();
            }

            // Perform the same drill on all specified views
            performDrill(drillRequestedEvent, vDataviews,
              linkedViewPanel.getDimensions());

            m_bInDrill = false;
          }
        }

        catch (Exception exception) {
          exception.printStackTrace();
        }
      }
    }
  }

  /**
   * Called when a layout change will be executed.  This event is fired
   * before the layout change executes.
   * This event may not be consumed.
   *
   * @param layoutChangedEvent a <code>LayoutChangedEvent</code> value
   *        that contains information about the changed layout.
   */
  public void layoutChanged(LayoutChangedEvent layoutChangedEvent) {
    // Check to see if we have any linked view panels
    BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
    if (linkedViewPanel == null) {
      return;
    }

    // If we are linking selections, ignore pivot events
    if (linkedViewPanel.getDimensions() != null) {
      return;
    }

    // Determine the relationships between the panels
    int nRelationship = linkedViewPanel.getRelationship();

    // Only perform layout change if we are in Master/Detail relationship
    if (nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL) {

      Query query = (Query)layoutChangedEvent.getSource();

      // Retrieve the master dataview
      Dataview dataviewMaster = linkedViewPanel.getDataviewMaster();

      if (((nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL) &&
           (dataviewMaster.getDataSource() == query)) ||
           (nRelationship == BILinkedViewPanel.RELATIONSHIP_BIDIRECTIONAL)) {

        try {
          // Check the drill flag to insure that we don't end up in a recursive
          // loop where we get drillRequested events in the middle of a drill
          if (!m_bInPivot) {
            m_bInPivot = true;

            // Perform the same drill on all detail views
            performPivot(layoutChangedEvent,
              linkedViewPanel.getDataviewsDetail(),
              linkedViewPanel.getDimensions());

            m_bInPivot = false;
          }
        }

        catch (Exception exception) {
          exception.printStackTrace();
        }
      }
    }
  }

  /**
   * Called when a selection will be changed.
   *
   * This event is fired before the actual selection and query are changed.
   * This event may not be consumed.
   *
   * @param selectionChangedEvent a <code>SelectionChangedEvent</code> value
   *        that contains information about the changed selection.
   */
  public void selectionChanged(SelectionChangedEvent selectionChangedEvent) {
    // Check to see if we have any linked view panels
    BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
    if (linkedViewPanel == null) {
      return;
    }

    // Determine the relationships between the panels
    int nRelationship = linkedViewPanel.getRelationship();

    // Only change selections if we have linked selections or are in
    // Master/Detail relationship
    if ((linkedViewPanel.getDimensions() != null) ||
        (nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL)) {

      // Retrieve the query associated with the event
      Query query = (Query)selectionChangedEvent.getSource();

      // Retrieve the master dataview
      Dataview dataviewMaster = linkedViewPanel.getDataviewMaster();

      if (((nRelationship == BILinkedViewPanel.RELATIONSHIP_MASTER_DETAIL) &&
           (dataviewMaster.getDataSource() == query)) ||
           (nRelationship == BILinkedViewPanel.RELATIONSHIP_BIDIRECTIONAL)) {

        try {
          // Check the Selection changing flag to insure that we don't end up in
          // a recursive loop where we get SelectionChanged events in the middle
          // of changing selections
          if (!m_bInSelectionChanging) {
            m_bInSelectionChanging = true;

            // Determine which views need to have their selections updated
            Vector vDataviews = null;
            if (nRelationship == BILinkedViewPanel.RELATIONSHIP_BIDIRECTIONAL) {
              // If we are bi-directional, apply the selection to all other
              // views
              vDataviews = linkedViewPanel.getDataviews(query);
            }
            else {
              // If we are master/detail, apply the selection to all other
              // detail views LinkedViewPanel.RELATIONSHIP_MASTER_DETAIL
              vDataviews = linkedViewPanel.getDataviewsDetail();
            }

            // Perform the same selection change on all specified views
            performChangeSelection(selectionChangedEvent, vDataviews,
              linkedViewPanel.getDimensions());

            m_bInSelectionChanging = false;
          }
        }

        catch (Exception exception) {
          exception.printStackTrace();
        }
      }
    }
  }

  /**
   * Notifies when a lazy data fetch has been completed.
   *
   * @param waitDataAvailableEvent a <code>WaitDataAvailableEvent</code> value
   *        that contains information about the fetch.
   *
   */
  public void waitDataAvailable(WaitDataAvailableEvent waitDataAvailableEvent) {
  }

  /**
   * Informs the view of changes to the actual data or metadata.
   *
   * @param dataChangedEvent a <code>DataChangedEvent</code> value
   *        that contains information about the change in the data.
   */
  public void viewDataChanged(DataChangedEvent dataChangedEvent) {
    // Launch a data available event
    viewDataAvailable(new DataAvailableEvent(dataChangedEvent.getSource(),
      DataAvailableEvent.DATA_AVAILABLE, dataChangedEvent.getDataAccess()));

    // Check to see if we have any linked view panels
    BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
    if (linkedViewPanel == null) {
      return;
    }

    // Retrieve the query associated with the current event
    Query query = (Query)dataChangedEvent.getSource();

    // For paging, we will treat the view associated with the query at the
    // master
    Dataview dataviewMaster = linkedViewPanel.getDataview(query);
    if (dataviewMaster != null) {

      try {
        // Check the paging flag to insure that we don't end up in a recursive
        // loop where we get dataChangedEvent events in the middle of a paging
        // operation
        if (!m_bInPaging) {
          m_bInPaging = true;

          performUpdate(dataviewMaster,
            linkedViewPanel.getDataviews(dataviewMaster), dataChangedEvent);

          m_bInPaging = false;
        }
      }

      catch (Exception exception) {
        exception.printStackTrace();
      }
    }
  }

  /**
   * Performs a change selection operation on each dimension of the specified
   * view based on a previous selection event.
   *
   * @param selectionChangedEvent a <code>SelectionChangedEvent</code> value
   *        associated with a <code>Query</code> whose selection has been
   *        changed.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that the selection should be applied
   *        to.
   *
   */
  public void performChangeSelection(SelectionChangedEvent
    selectionChangedEvent, Vector vDataviews) {

    // Make sure that the event and dataviews are not null
    if ((selectionChangedEvent != null) && (vDataviews != null)) {

      // Apply the selection to all specified views
      Enumeration enumerationDataviews = vDataviews.elements();
      while (enumerationDataviews.hasMoreElements()) {
        // Retrieve the current dataview
        Dataview dataview = (Dataview)enumerationDataviews.nextElement();

        // Retrieve the query associated with the dataview
        Query query = (Query)dataview.getDataSource();

        // Check for a non-null query
        if (query != null) {

          // Retrieve the selections associated with the event
          Selection[] selections = selectionChangedEvent.getSelections();

          if ((selections != null) && (selections.length != 0)) {
            // Retrieve the first selection
            Selection selection = selections[0];

            // Generate a QueryAccess based on the Query
            QueryAccess queryAccess = query.createQueryAccess();

            // Update the selection associated with the current dataview
          	queryAccess.setSelection(selection);

            // Update the query
            query.applyQueryAccess(queryAccess, true);

            // Free up the query access
            queryAccess.release();
          }
        }
      }
    }
  }

  /**
   * Performs a drill operation on each dimension of the specified view based
   * on a previous drill event.
   *
   * @param selectionChangedEvent a <code>SelectionChangedEvent</code> value
   *        associated with a <code>Query</code> whose selection has been
   *        changed.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that the selection should be applied
   *        to.
   * @param vstrDimensions a <code>Vector</code> value that represents the list
   *        of dimensions to perform selection changes on.
   *
   */
  public void performChangeSelection(SelectionChangedEvent
    selectionChangedEvent, Vector vDataviews, Vector vstrDimensions) {

    if ((selectionChangedEvent != null) && (vDataviews != null)) {

      if (vstrDimensions != null) {
        Enumeration enumeration = vstrDimensions.elements();
        while (enumeration.hasMoreElements()) {
          String strDimension = (String)enumeration.nextElement();

          // Retrieve the selections associated with the event
          Selection[] selections = selectionChangedEvent.getSelections();

          if ((selections != null) && (selections.length != 0)) {
            // Retrieve the first selection
            Selection selection = selections[0];

            if (selection != null) {
              String strDimensionSelection = selection.getDimension();
              if (strDimension.equals(strDimensionSelection)) {
                // Apply the selection to all specified views
                performChangeSelection(selectionChangedEvent, vDataviews);
              }
            }
          }
        }
      }
      else {
        // Apply the selection to all specified views
        performChangeSelection(selectionChangedEvent, vDataviews);
      }
    }
  }

  /**
   * Performs a drill operation on each dimension of the specified view based
   * on a previous drill event.
   *
   * @param drillRequestedEvent a <code>DrillRequestedEvent</code> value
   *        associated with a <code>Query</code> that has been drilled on.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that drill should be applied to.
   *
   * @throws InvalidStepArgException
   * @throws MetadataManagerException
   * @throws QueryException
   * @throws SelectionException
   */
  public void performDrill(DrillRequestedEvent drillRequestedEvent,
    Vector vDataviews)
    throws InvalidStepArgException, MetadataManagerException,
           QueryException, SelectionException {

    if ((drillRequestedEvent != null) && (vDataviews != null)) {

      // Apply the drill to all specified views
      Enumeration enumerationDataviews = vDataviews.elements();
      while (enumerationDataviews.hasMoreElements()) {
        Dataview dataview = (Dataview)enumerationDataviews.nextElement();
        Query query = (Query)dataview.getDataSource();

        if (query != null) {
          // Key Concept
          // Crosstab drills are different than graph drills based on level

          // Determine the type of drill to perform from the event
          // Check to see if we are drilling on a level (for example like Graph)
          if (drillRequestedEvent.getLevel() != null) {
            query.drill(drillRequestedEvent.getDimension(),
              drillRequestedEvent.getValue(),
              drillRequestedEvent.getHierarchy(),
              drillRequestedEvent.getLevel(), drillRequestedEvent.getAction(),
              drillRequestedEvent.getFlags());
          }
          else {
            // Perform non-level based drill (for example like Crosstab)
            query.drill(drillRequestedEvent.getDimension(),
              drillRequestedEvent.getValue(),
              drillRequestedEvent.getHierarchy(),
              drillRequestedEvent.getDelta(),
              drillRequestedEvent.getValueParent(),
              drillRequestedEvent.getQueryParent(),
              drillRequestedEvent.getParentQDR());
          }
        }
      }
    }
  }

  /**
   * Performs a drill operation on each dimension of the specified view based
   * on a previous drill event.
   *
   * @param drillRequestedEvent a <code>DrillRequestedEvent</code> value
   *        associated with a <code>Query</code> that has been drilled on.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that drill should be applied to.
   * @param vstrDimensions a <code>Vector</code> value that represents the list
   *        of dimensions to drill on.
   *
   * @throws InvalidStepArgException
   * @throws MetadataManagerException
   * @throws QueryException
   * @throws SelectionException
   */
  public void performDrill(DrillRequestedEvent drillRequestedEvent,
                            Vector vDataviews, Vector vstrDimensions)
      throws InvalidStepArgException, MetadataManagerException,
             QueryException, SelectionException {

    if ((drillRequestedEvent != null) && (vDataviews != null)) {

      if (vstrDimensions != null) {
        Enumeration enumeration = vstrDimensions.elements();
        while (enumeration.hasMoreElements()) {
          String strDimension = (String)enumeration.nextElement();

          if (strDimension.equals(drillRequestedEvent.getDimension())) {
            // Apply the drill to all specified views
            performDrill(drillRequestedEvent, vDataviews);
          }
        }
      }
      else {
        // Apply the drill to all specified views
        performDrill(drillRequestedEvent, vDataviews);
      }
    }
  }

  /**
   * Performs a pivot operation on each dimension of the specified view based
   * on a previous pivot event.
   *
   * @param layoutChangedEvent a <code>LayoutChangedEvent</code> value
   *        associated with a <code>Query</code> that has its layout changed.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that drill should be applied to.
   *
   * @throws InvalidStepArgException
   * @throws MetadataManagerException
   * @throws QueryException
   * @throws SelectionException
   */
  public void performPivot(LayoutChangedEvent layoutChangedEvent,
    Vector vDataviews)
    throws InvalidStepArgException, MetadataManagerException,
           QueryException, SelectionException {

    if ((layoutChangedEvent != null) && (vDataviews != null)) {

      // Apply the pivot to all specified views
      Enumeration enumerationDataviews = vDataviews.elements();
      while (enumerationDataviews.hasMoreElements()) {
        Dataview dataview = (Dataview)enumerationDataviews.nextElement();
        Query query = (Query)dataview.getDataSource();

        if (query != null) {
          query.pivot(layoutChangedEvent.getSourceDimension(), 
            layoutChangedEvent.getTargetDimension(),
            layoutChangedEvent.getFlags());
        }
      }
    }
  }

  /**
   * Performs a pivot operation on each dimension of the specified view based
   * on a previous pivot event.
   *
   * @param layoutChangedEvent a <code>LayoutChangedEvent</code> value
   *        associated with a <code>Query</code> that has its layout changed.
   * @param vDataviews a <code>Vector</code> value associated with the list of
   *        <code>Dataview</code> objects that drill should be applied to.
   * @param vstrDimensions a <code>Vector</code> value that represents the list
   *        of dimensions to drill on.
   *
   * @throws InvalidStepArgException
   * @throws MetadataManagerException
   * @throws QueryException
   * @throws SelectionException
   */
  public void performPivot(LayoutChangedEvent layoutChangedEvent,
                            Vector vDataviews, Vector vstrDimensions)
      throws InvalidStepArgException, MetadataManagerException,
             QueryException, SelectionException {

    if ((layoutChangedEvent != null) && (vDataviews != null)) {

      if (vstrDimensions != null) {
        Enumeration enumeration = vstrDimensions.elements();
        while (enumeration.hasMoreElements()) {
          String strDimension = (String)enumeration.nextElement();

          if (strDimension.equals(layoutChangedEvent.getSourceDimension())) {
            // Apply the pivot to all specified views
            performPivot(layoutChangedEvent, vDataviews);
          }
        }
      }
      else {
        // Apply the pivot to all specified views
        performPivot(layoutChangedEvent, vDataviews);
      }
    }
  }

  /**
   * Attempts to update the page based on the specified dimension member.
   * If the dimension member is not found, no page changes are made.
   *
   * @param  queryListener a <code>BILinkedQueryListener</code> value that represents
   *         a listener that allows us to retrieve a <code>DataAccess</code>
   *         and <code>DataDirector</code> used to retrieve metadata and specify
   *         pages.
   * @param  strDimensionMember a <code>String</code> value that represents
   *         dimension member to find.
   *
   * @return <code>boolean</code> which is <code>true</code> when the page
   *         has been updated and <code>false</code> otherwise.
   *
   * @throws DataDirectorException
   * @throws EdgeOutOfRangeException
   * @throws LayerOutOfRangeException
   * @throws SliceOutOfRangeException
   */
  public boolean updatePage(BILinkedQueryListener queryListener,
    String strDimensionMember)
    throws DataDirectorException, EdgeOutOfRangeException,
           LayerOutOfRangeException, SliceOutOfRangeException {

    // Check for null parameters
    if ((queryListener == null) || (strDimensionMember == null)) {
      return false;
    }

    // Retrieve DataAccess and DataDirector required to update page
    DataAccess dataAccess = queryListener.getDataAccess();
    DataDirector dataDirector = queryListener.getDataDirector();

    // Check for non-null DataAccess and DataDirector
    if ((dataAccess == null) || (dataDirector == null)) {
      return false;
    }

    // Iterate over all edges
    int nEdges  = dataAccess.getEdgeCount();
    for (int nEdge = 0; nEdge < nEdges; nEdge++) {

      // Iterate over all layers
      int nLayers = dataAccess.getLayerCount(nEdge);
      for (int nLayer = 0; nLayer < nLayers; nLayer++) {

        // Iterate over all slices
        int nSlices = dataAccess.getSliceMemberCount(nEdge, nLayer);
        for (int nSlice = 0; nSlice < nSlices; nSlice++) {
          // Attempt to retrieve the member at the current position
          String strMember =
            (String) dataAccess.getMemberMetadata(nEdge, nLayer,
              nSlice, MetadataMap.METADATA_VALUE);

          // Check to see if this member is the one we are looking for
          if (strMember != null) {
            if (strMember.equalsIgnoreCase(strDimensionMember)) {

              // Set the page based on the current position
              dataDirector.changeEdgeCurrentSlice(nEdge, nSlice);

              // Page has been updated
              return true;
            }
          }
        }
      }
    }

    // Page has not been updated
    return false;
  }

  /**
   * Updates the page associated with the detail dataview based on the specified
   * master dataview.
   *
   * This is accomplished by mapping the master slice into a QDR which can be
   * then be used to find the selected master dimension members in the
   * specified detail dataviews.
   *
   * @param  dataviewMaster a <code>Dataview</code> value that represents
   *         the master Dataview.
   * @param  dataviewDetail a <code>Dataview</code> value that represents
   *         the detail DataView.
   *
   * @return <code>boolean</code> which is <code>true</code> when the page
   *         has been updated and <code>false</code> otherwise.
   *
   * @throws DataDirectorException
   * @throws EdgeOutOfRangeException
   * @throws LayerOutOfRangeException
   * @throws SliceOutOfRangeException
   */
  public boolean updatePageQDR(Dataview dataviewMaster,
    Dataview dataviewDetail)
    throws DataDirectorException, EdgeOutOfRangeException,
           LayerOutOfRangeException, SliceOutOfRangeException {

    boolean bResult = false;

    // Verify that the views are non-null
    if ((dataviewMaster != null) && (dataviewDetail != null)) {

      // Check to see if we have any linked view panels
      BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
      if (linkedViewPanel == null) {
        return false;
      }

      // Retrieve the BILinkedQueryListener from the master view
      BILinkedQueryListener queryListener =
        linkedViewPanel.getQueryListener(dataviewMaster);

      // Make sure that QueryListener is non-null
      if (queryListener == null) {
        return false;
      }

      // Retrieve a DataAccess
      DataAccess dataAccess = queryListener.getDataAccess();

      // Check for non-null DataAccess
      if (dataAccess == null) {
        return false;
      }

      // Determine what the master's current slice is
      int nSlice = dataAccess.getEdgeCurrentSlice(DataDirector.PAGE_EDGE);

      // Determine the QDR associated with the master's current slice
      QDR qdrPage = dataAccess.getSliceQDR(DataDirector.PAGE_EDGE,
        nSlice, DataAccess.QDR_WITH_PAGE);

      // Retrieve the BILinkedQueryListener from the detail view
      queryListener = linkedViewPanel.getQueryListener(dataviewDetail);
      if (queryListener == null) {
        return false;
      }

      // Initialize the dimension member
      String strDimensionMember = null;

      // Make sure that our QDR is not null
      if (qdrPage != null) {
        // Retrieve the currently linked dimensions
        Vector vDimensions = linkedViewPanel.getDimensions();

        // Continue if we have linked dimensions
        if ((vDimensions != null) && (!vDimensions.isEmpty())) {

          // Iterate over each dimension
          Enumeration enumeration = vDimensions.elements();
          while (enumeration.hasMoreElements()) {

            // Retrieve the dimension at our current position
            String strDimension = (String) enumeration.nextElement();
            if (strDimension != null) {
              // Attempt to find the currently selected dimension member
              QDRMember qdrMember = qdrPage.getDimMember(strDimension);
              if (qdrMember != null) {

                // Retrieve the dimension member
                strDimensionMember = (String) qdrMember.getData();

                // Attempt to update the detail view based on the dimension
                // member found.
                bResult = updatePage(queryListener, strDimensionMember);
              }
            }
          }
        }
      }
    }

    return bResult;
  }

  /**
   * Updates the page associated with the detail dataview based on the specified
   * master dataview.
   *
   * This is accomplished by applying the master slice to each detail dataview.
   *
   * This method should only be used when the underlying data and layout are
   * the same in each master and detail dataview.
   *
   * @param  dataviewMaster a <code>Dataview</code> value that represents
   *         the master Dataview.
   * @param  dataviewDetail a <code>Dataview</code> value that represents
   *         the detail DataView.
   *
   * @throws DataDirectorException
   * @throws EdgeOutOfRangeException
   * @throws LayerOutOfRangeException
   * @throws SliceOutOfRangeException
   */
  public void updatePage(Dataview dataviewMaster, Dataview dataviewDetail)
    throws DataDirectorException, EdgeOutOfRangeException,
           LayerOutOfRangeException, SliceOutOfRangeException {

    // Verify that the views are non-null
    if ((dataviewMaster != null) && (dataviewDetail != null)) {

      // Check to see if we have any linked view panels
      BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
      if (linkedViewPanel == null) {
        return;
      }

      // Retrieve the QueryListener from the master view
      BILinkedQueryListener queryListener =
        linkedViewPanel.getQueryListener(dataviewMaster);

      // Determine what the master's current slice is
      int nSlice =
        queryListener.getDataAccess().getEdgeCurrentSlice(
          DataDirector.PAGE_EDGE);

      // Retrieve the BILinkedQueryListener from the detail view
      queryListener = linkedViewPanel.getQueryListener(dataviewDetail);

      // Update the slice of the detail dataview based on the master
      queryListener.getDataDirector().changeEdgeCurrentSlice(
        DataDirector.PAGE_EDGE, nSlice);
    }
  }

  /**
   * Updates the each detail dataview based on the specified master dataview.
   *
   * @param  dataviewMaster a <code>Dataview</code> value that represents
   *         the master Dataview associated with this <code>LinkedViewPanel
   *         </code>.
   * @param  vDataviewDetail a <code>Vector</code> value that represents
   *         the list of detail DataViews associated with this <code>
   *         LinkedViewPanel</code>.
   * @param  dataChangedEvent a <code>DataChangedEvent</code> that contains
   *         information about the change in the data.
   *
   * @throws DataDirectorException
   * @throws EdgeOutOfRangeException
   * @throws LayerOutOfRangeException
   * @throws SliceOutOfRangeException
   */
  public void performUpdate(Dataview dataviewMaster, Vector vDataviewDetails,
                             DataChangedEvent dataChangedEvent)
    throws DataDirectorException, EdgeOutOfRangeException,
           LayerOutOfRangeException, SliceOutOfRangeException {

    // Verify that the parameters are non-null
    if ((dataviewMaster == null) || (vDataviewDetails == null) ||
        (dataChangedEvent == null)) {
      return;
    }

    // Perform dataview master operation on each detail dataview
    Enumeration enumeration = vDataviewDetails.elements();
    while (enumeration.hasMoreElements()) {

      // Retrieve the current detail dataview
      Dataview dataviewDetail = (Dataview)enumeration.nextElement();

      // Perform update operation based on DataChangedEvent
      switch (dataChangedEvent.getType()) {

        // Peform a paging operation
        case DataChangedEvent.PAGE_CHANGE:

          // Check to see if we have any linked view panels
          BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
          if (linkedViewPanel == null) {
            return;
          }

          // Determine how to update page based on the linked dimensions
          if (linkedViewPanel.getDimensions() != null) {
            updatePageQDR(dataviewMaster, dataviewDetail);
          }
          else {
            updatePage(dataviewMaster, dataviewDetail);
          }

          // This is a temporary work around fix for:
          // Bug 2153972: GridViewPagingControl does not honor enable status
          //              when creating new page items
          //
          // Make sure that disable paged items remain disabled after a page
          // update.
          updateEnableStatus();
          break;

        default:
          break;
      }
    }
  }

  /**
   * Informs the view that data is ready or revoked.
   *
   * @param e Information about the data provider.
   */
  public void viewDataAvailable(DataAvailableEvent e) {
    setDataAccess(e.getDataAccess());
  }

  /**
   * Specifies the <code>DataAccess</code> object.
   *
   * @param dataAccess a <code>DataAccess</code> value to associate with the
   *        <code>QueryListener</code>.
   */
  public void setDataAccess(DataAccess dataAccess) {
    m_dataAccess = dataAccess;
  }

  /**
   *  Retrieves the <code>DataAccess</code> associated with the <code>
   *  BILinkedQueryListener</code>.
   *
   *  @return <code>DataAccess</code> associated with the 
   *  <code>BILinkedQueryListener</code>.
   *
   */

  public DataAccess getDataAccess() {
    return m_dataAccess;
  }

  /**
   * Specifies the <code>DataDirector</code> object.
   *
   * @param dataDirector a <code>DataDirector</code> value to associate with the
   *        <code>BILinkedQueryListener</code>.
   */
  public void setDataDirector(DataDirector dataDirector) {
    m_dataDirector = dataDirector;
  }

  /**
   *  Retrieves the <code>DataDirector</code> associated with the <code>
   *  BILinkedQueryListener</code>.
   *
   *  @return <code>DataDirector</code> associated with the 
   *  <code>BILinkedQueryListener</code>.
   *
   */
  public DataDirector getDataDirector() {
    return m_dataDirector;
  }

  /**
   *  Retrieves the <code>Query</code> associated with the 
   *  <code>BILinkedQueryListener</code>.
   *
   *  @return <code>Query</code> associated with the 
   *  <code>BILinkedQueryListener</code>.
   *
   */
  public Query getQuery() {
    return m_query;
  }

  /**
   *  Retrieves the <code>LinkedViewPanel</code> associated with the
   *  <code>BILinkedQueryListener</code>.
   *
   *  @return <code>BILinkedViewPanel</code> representing the list of linked
   *          panels associated with the <code>BILinkedQueryListener</code>.
   *
   */
  public BILinkedViewPanel getLinkedViewPanel() {
    return m_linkedViewPanel;
  }

  /**
   * Specifies the <code>LinkedViewPanel</code> object.
   *
   * @param linkedViewPanel a <code>LinkedViewPanel</code> value to associate
   *        with the <code>BILinkedQueryListener</code>.
   */
  public void setLinkedViewPanel(BILinkedViewPanel linkedViewPanel) {
    m_linkedViewPanel = linkedViewPanel;
  }

  /////////////////////
  //
  // Protected Methods
  //
  /////////////////////

  /////////////////////
  //
  // Private Methods
  //
  /////////////////////

  /**
   * Update the status of each detail dataview based on its current status.
   *
   * This is a temporary work around fix for:
   *  Bug 2153972: GridViewPagingControl does not honor enable status cwhen
   *               creating new page items
   *
   * Make sure that disabled page items remain disabled after a page update.
   *
   */
  private void updateEnableStatus() {
    // Check to see if we have any linked view panels
    BILinkedViewPanel linkedViewPanel = getLinkedViewPanel();
    if (linkedViewPanel == null) {
      return;
    }

    // Get the list if detail dataviews
    Vector vDetailDataviews = linkedViewPanel.getDataviewsDetail();
    if (vDetailDataviews != null && !vDetailDataviews.isEmpty()) {
      // Update the enabled status of add detail dataviews
      Enumeration enumerationDataviews = vDetailDataviews.elements();
      while (enumerationDataviews.hasMoreElements()) {
        Dataview dataview = (Dataview)enumerationDataviews.nextElement();
        dataview.setEnabled(dataview.isEnabled());
      }
    }
  }
}
