/**
   * Actively performs the binding from the CForms-form to the ObjectModel wrapped in a jxpath
   * context
   */
  public void doSave(Widget frmModel, JXPathContext jxpc) throws BindingException {
    Widget widget = selectWidget(frmModel, this.fieldId);
    Object value = widget.getValue();
    if (value != null && convertor != null) {
      value = convertor.convertToString(value, convertorLocale, null);
    }

    Object oldValue = jxpc.getValue(this.xpath);
    if (getLogger().isDebugEnabled()) {
      getLogger().debug("value= " + value + " -- oldvalue=" + oldValue);
    }

    boolean update = false;

    if ((value == null && oldValue != null) || value != null && !value.equals(oldValue)) {
      // first update the value itself
      jxpc.createPathAndSetValue(this.xpath, value);

      // now perform any other bindings that need to be performed when the value is updated
      JXPathContext subContext = null;
      try {
        subContext = jxpc.getRelativeContext(jxpc.getPointer(this.xpath));
      } catch (JXPathException e) {
        // if the value has been set to null and the underlying model is a bean, then
        // JXPath will not be able to create a relative context
        if (getLogger().isDebugEnabled()) {
          getLogger()
              .debug("(Ignorable) problem binding field " + widget.getRequestParameterName(), e);
        }
      }
      if (subContext != null) {
        this.updateBinding.saveFormToModel(frmModel, subContext);
      }

      update = true;
    }

    if (getLogger().isDebugEnabled()) {
      getLogger()
          .debug("done saving " + this + " -- value= " + value + " -- on-update == " + update);
    }
  }
  /**
   * Looks for a policy node at the specified contextual location for the specified object.
   *
   * <p>If a policy node that contains the object name is not found, a search is also done for the
   * default policy for all objects, with a special name ('*').
   *
   * @param xpathContext The XPath context around the agent configuration XML document
   * @param currentPointer A pointer to a particular node in the document to start searching from,
   *     such as a <code>&lt;zone&gt;</code>, <code>&lt;template&gt;</code>, or <code>&lt;agent&gt;
   *     </code> node.
   * @param policyPath The subPath, such as "requestPolicy"
   * @param objectName
   * @return
   */
  private Element findPolicy(
      JXPathContext xpathContext, Pointer currentPointer, String policyPath, String objectName) {

    if (currentPointer != null && currentPointer.getNode() != null) {
      String objectPolicyPath =
          "policy/" + policyPath + "/object[contains(@name,'" + objectName + "')]";
      JXPathContext zoneContext = xpathContext.getRelativeContext(currentPointer);
      Element zonePolicy = (Element) zoneContext.selectSingleNode(objectPolicyPath);
      if (zonePolicy != null) {
        return zonePolicy;
      }

      // Look for default policy for all objects at the zone level
      zonePolicy =
          (Element) zoneContext.selectSingleNode("policy/" + policyPath + "/object[@name='*']");
      if (zonePolicy != null) {
        return zonePolicy;
      }
    }
    return null;
  }
  public void doSave(Widget frmModel, JXPathContext jctx) throws BindingException {
    // (See comment in doLoad about type checking and throwing a meaningful exception.)
    Repeater repeater = (Repeater) selectWidget(frmModel, this.repeaterId);

    // Perform shortcut binding if the repeater is empty
    // and the deleteIfEmpty config option is selected.
    if (repeater.getSize() == 0 && this.deleteIfEmpty) {
      // Delete all of the old data for this repeater.
      jctx.removeAll(this.repeaterPath);

      // Otherwise perform the normal save binding.
    } else {

      // Narrow to the repeater context, creating the path if it did not exist.
      JXPathContext repeaterContext = jctx.getRelativeContext(jctx.createPath(this.repeaterPath));

      // Start by deleting all of the old row data.
      repeaterContext.removeAll(this.rowPath);

      // Verify that repeater is not empty and has an insert row binding.
      if (repeater.getSize() > 0) {
        if (this.insertRowBinding != null) {

          // register the factory!
          // this.insertRowBinding.saveFormToModel(repeater, repeaterContext);

          // Iterate through the repeater rows.
          for (int i = 0; i < repeater.getSize(); i++) {

            // Narrow to the repeater row context.
            Pointer rowPointer = repeaterContext.getPointer(this.rowPathInsert);
            JXPathContext rowContext = repeaterContext.getRelativeContext(rowPointer);

            // Variables used for virtual rows.
            // They are initialized here just to keep the compiler happy.
            Node rowNode = null;
            Node virtualNode = null;

            // If virtual rows are requested, create a temporary node and
            // narrow the context to this initially empty new virtual row.
            if (virtualRows) {
              rowNode = (Node) rowContext.getContextBean();
              Document document = rowNode.getOwnerDocument();
              virtualNode = document.createElementNS(null, "virtual");
              Node fakeDocElement = document.getDocumentElement().cloneNode(false);
              fakeDocElement.appendChild(virtualNode);
              rowContext = JXPathContext.newContext(repeaterContext, fakeDocElement);
              rowContext = rowContext.getRelativeContext(rowContext.getPointer("virtual"));
            }

            // Perform the insert row binding
            this.insertRowBinding.saveFormToModel(repeater, rowContext);

            // Perform the save row binding.
            this.rowBinding.saveFormToModel(repeater.getRow(i), rowContext);

            // If virtual rows are requested, finish by appending the
            // children of the virtual row to the real context node.
            if (virtualRows) {
              NodeList list = virtualNode.getChildNodes();
              int count = list.getLength();
              for (int j = 0; j < count; j++) {
                // The list shrinks when a child is appended to the context
                // node, so we always reference the first child in the list.
                rowNode.appendChild(list.item(0));
              }
            }
            getLogger().debug("bound new row");
          }
        } else {
          getLogger()
              .warn(
                  "TempRepeaterBinding has detected rows to insert, "
                      + "but misses the <on-insert-row> binding to do it.");
        }
      }
    }
  }
  public void doLoad(Widget frmModel, JXPathContext jctx) throws BindingException {
    // (There should be a general widget type checker for all the bindings to use,
    // coupled with a general informative exception class to throw if the widget is
    // of the wrong type or null.)
    Repeater repeater = (Repeater) selectWidget(frmModel, this.repeaterId);
    if (repeater == null) {
      String fullId = frmModel.getRequestParameterName();
      if (fullId == null || fullId.length() == 0) {
        fullId = "";
      } else {
        fullId = fullId + ".";
      }
      throw new RuntimeException(
          "TempRepeaterJXPathBinding: Repeater \""
              + fullId
              + this.repeaterId
              + "\" does not exist ("
              + frmModel.getLocation()
              + ")");
    }

    // Start by clearing the repeater, if necessary.
    if (this.clearOnLoad) {
      repeater.clear();
    }

    // Find the location of the repeater data.
    Pointer repeaterPointer = jctx.getPointer(this.repeaterPath);

    // Check if there is data present.
    //
    // (Otherwise, should we check the leniency config option
    // to decide whether to be silent or throw an exception?)
    if (repeaterPointer != null) {

      // Narrow to repeater context.
      JXPathContext repeaterContext = jctx.getRelativeContext(repeaterPointer);

      // Build a jxpath iterator for the repeater row pointers.
      Iterator rowPointers = repeaterContext.iteratePointers(this.rowPath);

      // Iterate through the rows of data.
      int rowNum = 0;
      while (rowPointers.hasNext()) {

        // Get or create a row widget.
        Repeater.RepeaterRow thisRow;
        if (repeater.getSize() > rowNum) {
          thisRow = repeater.getRow(rowNum);
        } else {
          thisRow = repeater.addRow();
        }
        rowNum++;

        // Narrow to the row context.
        Pointer rowPointer = (Pointer) rowPointers.next();
        JXPathContext rowContext = repeaterContext.getRelativeContext(rowPointer);

        // If virtual rows are requested, place a deep clone of the row data
        // into a temporary node, and narrow the context to this virtual row.
        //
        // (A clone of the data is used to prevent modifying the source document.
        // Otherwise, the appendChild method would remove the data from the source
        // document.  Is this protection worth the penalty of a deep clone?)
        //
        // (This implementation of virtual rows currently only supports DOM
        // bindings, but could easily be extended to support other bindings.)

        if (virtualRows) {
          Node repeaterNode = (Node) repeaterPointer.getNode();
          Node virtualNode = repeaterNode.getOwnerDocument().createElementNS(null, "virtual");
          Node node = (Node) rowPointer.getNode();
          Node clone = node.cloneNode(true);
          Node fakeDocElement = node.getOwnerDocument().getDocumentElement().cloneNode(false);
          virtualNode.appendChild(clone);
          fakeDocElement.appendChild(virtualNode);
          rowContext = JXPathContext.newContext(repeaterContext, fakeDocElement);
          rowContext = rowContext.getRelativeContext(rowContext.getPointer("virtual"));
        }

        // Finally, perform the load row binding.
        this.rowBinding.loadFormFromModel(thisRow, rowContext);
      }
    }

    if (getLogger().isDebugEnabled()) getLogger().debug("done loading rows " + this);
  }