@Override
  public Object convertToKey(
      FacesContext context, String keyString, UIComponent component, Converter converter) {
    Object convertedKey = treeDataModel.convertToKey(context, keyString, component, converter);

    if (convertedKey != null) {
      final TreeRowKey treeRowKey = (TreeRowKey) convertedKey;
      try {
        walk(
            context,
            NULL_VISITOR,
            new TreeRange() {

              public boolean processChildren(TreeRowKey rowKey) {
                return rowKey == null || rowKey.isSubKey(treeRowKey);
              }

              public boolean processNode(TreeRowKey rowKey) {
                return this.processChildren(rowKey) || rowKey.equals(treeRowKey);
              }
            },
            null);
      } catch (IOException e) {
        context.getExternalContext().log(e.getLocalizedMessage(), e);

        return null;
      }
    }

    return convertedKey;
  }
 public void walkModel(
     FacesContext context,
     DataVisitor visitor,
     Range range,
     Object key,
     Object argument,
     boolean last)
     throws IOException {
   treeDataModel.walkModel(context, visitor, range, key, argument, last);
 }
  @Override
  public void removeNode(Object rowKey) {
    super.removeNode(rowKey);

    if (treeDataModel != null) {
      Object savedRowKey = treeDataModel.getRowKey();

      try {
        treeDataModel.setRowKey(getRowKey());
        treeDataModel.removeNode(rowKey);
      } finally {
        try {
          treeDataModel.setRowKey(savedRowKey);
        } catch (Exception e) {
          log.error(e.getMessage(), e);
        }
      }
    }
  }
  @Override
  public void addNode(Object parentRowKey, TreeNode newNode, Object id) {
    super.addNode(parentRowKey, newNode, id);

    if (treeDataModel != null) {
      Object savedRowKey = treeDataModel.getRowKey();

      try {
        treeDataModel.setRowKey(getRowKey());
        treeDataModel.addNode(parentRowKey, newNode, id);
      } finally {
        try {
          treeDataModel.setRowKey(savedRowKey);
        } catch (Exception e) {
          log.error(e.getMessage(), e);
        }
      }
    }
  }
  @Override
  public TreeNode getModelTreeNode() {
    TreeNode node = null;
    if (treeDataModel != null) {
      Object savedRowKey = treeDataModel.getRowKey();

      try {
        treeDataModel.setRowKey(getRowKey());
        node = treeDataModel.getModelTreeNode();
      } finally {
        try {
          treeDataModel.setRowKey(savedRowKey);
        } catch (Exception e) {
          log.error(e.getMessage(), e);
        }
      }
    }

    return node;
  }
  public void walk(
      FacesContext context,
      final DataVisitor dataVisitor,
      Range range,
      Object rowKey,
      Object argument,
      boolean last)
      throws IOException {

    T cachedTreeNode = locateTreeNode((TreeRowKey) rowKey);
    T treeNode = treeDataModel.locateTreeNode((TreeRowKey) rowKey);

    if (treeNode != null) {
      if (cachedTreeNode == null
          || (nodeAdaptor.isLeaf(cachedTreeNode) && !nodeAdaptor.isLeaf(treeNode))) {
        // fill cache
        treeDataModel.walk(context, new CacheFillingVisitor(), range, rowKey, argument, last);
      }

      super.walk(context, dataVisitor, range, rowKey, argument, last);
    }
  }
  public boolean isLeaf() {
    TreeRowKey rowKey = (TreeRowKey) getRowKey();
    T treeNode = locateTreeNode(rowKey);
    if (treeNode != null && !nodeAdaptor.isLeaf(treeNode)) {
      return false;
    }

    treeNode = treeDataModel.locateTreeNode(rowKey);
    if (treeNode != null) {
      return nodeAdaptor.isLeaf(treeNode);
    }

    return false;
  }
 public CacheableTreeDataModel(TreeDataModel<T> model, MissingNodeHandler<T> missingNodeHandler) {
   super(model.getClazz(), model.getNodeAdaptor(), missingNodeHandler);
   setWrappedData(missingNodeHandler.handleMissingNode(null, null));
   setTreeDataModel(model);
 }