public static void updateTreeNode(PO po) {
    int nodeId = po.get_ID();
    if (nodeId < 0) {
      return; // nothing to do, because our PO has no ID to match against a tree node
    }
    final int AD_Table_ID = po.get_Table_ID();
    if (!MTree.hasTree(AD_Table_ID)) {
      return;
    }
    final String nodeTableName = MTree.getNodeTableName(AD_Table_ID);
    if (nodeTableName == null) {
      return;
    }

    //
    // services
    final IPOTreeSupportFactory treeSupportFactory = Services.get(IPOTreeSupportFactory.class);

    final String trxName = po.get_TrxName();
    final IPOTreeSupport treeSupport = treeSupportFactory.get(po.get_TableName());

    if (po.is_ValueChanged("IsSummary") && !po.get_ValueAsBoolean("IsSummary")) {
      // Move all its children to parent
      final List<MTreeNode> children =
          fetchNodes(AD_Table_ID, "Parent_ID=?", new Object[] {nodeId}, trxName);
      if (children.size() > 0) {
        throw new AdempiereException("TreeNodeHasChildren"); // TODO: translate
      }
    }

    if (treeSupport.isParentChanged(po)) {
      int parentId = treeSupport.getParent_ID(po);
      int oldParentId = treeSupport.getOldParent_ID(po);
      int seqNo = -1; // compute

      final String sql =
          "SELECT AD_Tree_ID, TreeType FROM AD_Tree WHERE AD_Table_ID=? AND AD_Client_ID=?";
      PreparedStatement pstmt = null;
      ResultSet rs = null;
      try {
        pstmt = DB.prepareStatement(sql, trxName);
        DB.setParameters(pstmt, new Object[] {AD_Table_ID, po.getAD_Client_ID()});
        rs = pstmt.executeQuery();
        while (rs.next()) {
          int AD_Tree_ID = rs.getInt(COLUMNNAME_AD_Tree_ID);
          String treeType = rs.getString(COLUMNNAME_TreeType);
          updateNode(
              po, AD_Tree_ID, treeType, AD_Table_ID, nodeId, parentId, oldParentId, seqNo, trxName);
        }
      } catch (SQLException e) {
        throw new DBException(e);
      } finally {
        DB.close(rs, pstmt);
        rs = null;
        pstmt = null;
      }
    }
  }
  private void updateNode(MTreeNode node, int parentId, int seqNo, String trxName) {
    //
    // services
    final IPOTreeSupportFactory treeSupportFactory = Services.get(IPOTreeSupportFactory.class);

    final int AD_Tree_ID = this.getAD_Tree_ID();
    if (node.getAD_Tree_ID()
        < 0) // TODO: workaround, we should instantiate the MTreeNode by giving the tree
    {
      node.setAD_Tree_ID(this.getAD_Tree_ID());
      node.setAD_Table_ID(this.getAD_Table_ID());
    }

    if (node.getAD_Tree_ID() != AD_Tree_ID)
      throw new IllegalArgumentException(
          "AD_Tree_ID<>"
              + AD_Tree_ID
              + " (node:"
              + node
              + ", AD_Tree_ID="
              + node.getAD_Tree_ID()
              + ")");

    final int nodeId = node.getNode_ID();
    final int oldParentId = node.getParent_ID();

    updateNode(
        null,
        this.getAD_Tree_ID(),
        this.getTreeType(),
        this.getAD_Table_ID(),
        nodeId,
        parentId,
        oldParentId,
        seqNo,
        trxName);

    node.setParent_ID(parentId);
    node.setSeqNo(seqNo);

    treeSupportFactory
        .get(getSourceTableName())
        .setParent_ID(this, node.getNode_ID(), parentId, trxName);
    listeners.onParentChanged(node.getAD_Table_ID(), nodeId, parentId, oldParentId, trxName);
  }
  public void verifyTree() {
    final IPOTreeSupportFactory treeSupportFactory = Services.get(IPOTreeSupportFactory.class);

    final String nodeTableName = getNodeTableName();
    final String sourceTableName = getSourceTableName();
    final String sourceTableKey = getSourceKeyColumnName();
    final int AD_Client_ID = getAD_Client_ID();

    final IPOTreeSupport treeSupport = treeSupportFactory.get(getSourceTableName());
    final String sourceTableWhereClause = treeSupport.getWhereClause(this);

    // Delete unused
    final StringBuilder sql = new StringBuilder();
    sql.append("DELETE FROM ")
        .append(nodeTableName)
        .append(" WHERE AD_Tree_ID=")
        .append(getAD_Tree_ID())
        .append(" AND Node_ID<>0") // don't delete root node
        .append(" AND Node_ID NOT IN (SELECT ")
        .append(sourceTableKey)
        .append(" FROM ")
        .append(sourceTableName)
        .append(" WHERE AD_Client_ID=")
        .append(AD_Client_ID);
    if (!Check.isEmpty(sourceTableWhereClause, true)) {
      sql.append(" AND (").append(sourceTableWhereClause).append(")");
    }
    sql.append(")");
    log.trace(sql.toString());
    //
    final int deletes = DB.executeUpdateEx(sql.toString(), get_TrxName());
    log.info(getName() + " Deleted #" + deletes);

    // Check and create root node
    {
      final Query query =
          new Query(getCtx(), nodeTableName, "AD_Tree_ID=? AND Node_ID=?", get_TrxName())
              .setParameters(getAD_Tree_ID(), ROOT_Node_ID);
      if (!query.match()) {
        createNode(ROOT_Node_ID, ROOT_Node_ID);
        log.info(getName() + " Root Node Created");
      }
    }

    if (!isAllNodes()) {
      return;
    }
    String sqlParentId = treeSupport.getParentIdSQL();
    boolean updateParents = true;
    if (Check.isEmpty(sqlParentId, true)) {
      sqlParentId = "" + ROOT_Node_ID;
      updateParents = true;
    }

    // Insert new
    sql.setLength(0);
    sql.append("SELECT ")
        .append(sourceTableKey) // 1
        .append(",")
        .append(sqlParentId) // 2
        .append(" FROM ")
        .append(sourceTableName)
        .append(" WHERE AD_Client_ID=")
        .append(AD_Client_ID);
    if (!Check.isEmpty(sourceTableWhereClause, true)) {
      sql.append(" AND (").append(sourceTableWhereClause).append(")");
    }
    sql.append(" AND ")
        .append(sourceTableKey)
        .append("  NOT IN (SELECT Node_ID FROM ")
        .append(nodeTableName)
        .append(" WHERE AD_Tree_ID=")
        .append(getAD_Tree_ID())
        .append(")");
    //
    int inserted = 0;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql.toString(), get_TrxName());
      rs = pstmt.executeQuery();
      while (rs.next()) {
        int nodeId = rs.getInt(1);
        int parentId = rs.getInt(2);
        if (nodeId == ROOT_Node_ID)
          throw new AdempiereException(
              "Tree is not supported for this table because it contains a record that has the same ID as current hardcoded root id");
        createNode(nodeId, parentId);
        inserted++;
      }
    } catch (SQLException e) {
      throw new DBException(e, sql.toString());
    } finally {
      DB.close(rs, pstmt);
      rs = null;
      pstmt = null;
    }

    //
    // Update parents from PO table:
    if (updateParents) {
      // TODO: implement update parents logic
    }
    log.info(getName() + " Inserted #" + inserted);
  }
  /**
   * ************************************************************************ Insert id data into
   * Tree
   *
   * @return true if inserted
   */
  protected static boolean insertTreeNode(PO po) {
    //
    // services
    final IPOTreeSupportFactory treeSupportFactory = Services.get(IPOTreeSupportFactory.class);

    // TODO: check self contained tree
    final int AD_Table_ID = po.get_Table_ID();
    if (!MTree.hasTree(AD_Table_ID)) {
      return false;
    }
    final int id = po.get_ID();
    final int AD_Client_ID = po.getAD_Client_ID();
    final String treeTableName = MTree.getNodeTableName(AD_Table_ID);
    final String trxName = po.get_TrxName();
    final Logger log = po.get_Logger();

    final IPOTreeSupport treeSupport = treeSupportFactory.get(po.get_TableName());
    final int AD_Tree_ID = treeSupport.getAD_Tree_ID(po);
    int parentId = treeSupport.getParent_ID(po);
    if (parentId < 0 || parentId == IPOTreeSupport.UNKNOWN_ParentID) {
      parentId = ROOT_Node_ID;
    }
    //
    // Insert
    final StringBuilder sb =
        new StringBuilder("INSERT  INTO ")
            .append(treeTableName)
            .append(" (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, ")
            .append("AD_Tree_ID, Node_ID, Parent_ID, SeqNo) ")
            //
            .append(
                "SELECT t.AD_Client_ID,0, 'Y', now(), "
                    + po.getUpdatedBy()
                    + ", now(), "
                    + po.getUpdatedBy()
                    + ",")
            .append("t.AD_Tree_ID, ")
            .append(id)
            .append(", ")
            .append(parentId)
            .append(", 999 ")
            .append("FROM AD_Tree t ")
            .append("WHERE t.AD_Client_ID=")
            .append(AD_Client_ID)
            .append(" AND t.IsActive='Y'");
    if (AD_Tree_ID > 0) {
      sb.append(" AND t.AD_Tree_ID=").append(AD_Tree_ID);
    } else
    // std trees
    {
      sb.append(" AND t.IsAllNodes='Y' AND t.AD_Table_ID=").append(AD_Table_ID);
    }
    // Duplicate Check
    sb.append(" AND NOT EXISTS (SELECT * FROM ")
        .append(treeTableName)
        .append(" e ")
        .append("WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=")
        .append(id)
        .append(")");
    //
    int no = DB.executeUpdateEx(sb.toString(), trxName);
    if (no < 0) {
      log.warn("#" + no + " - AD_Table_ID=" + AD_Table_ID);
      return false;
    }
    log.debug("#" + no + " - AD_Table_ID=" + AD_Table_ID);
    // MigrationLogger.instance.logMigrationSQL(po, sb.toString()); // metas: not needed because
    // it's called directly from PO

    listeners.onNodeInserted(po);

    return true;
  } // insert_Tree