/**
   * Traverses the node, including its children (recursive), and gathers all the node ids.
   *
   * @param node the target node
   * @param set set to store ids, if <tt>null</tt> a new set will be created
   * @param onlyCustomId whether to only store custom assigned ids (ie. {@link
   *     org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()}
   * @param includeAbstract whether to include abstract nodes (ie. {@link
   *     org.apache.camel.model.ProcessorDefinition#isAbstract()}
   * @return the set with the found ids.
   */
  public static Set<String> gatherAllNodeIds(
      ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) {
    if (node == null) {
      return set;
    }

    // skip abstract
    if (node.isAbstract() && !includeAbstract) {
      return set;
    }

    if (set == null) {
      set = new LinkedHashSet<String>();
    }

    // add ourselves
    if (node.getId() != null) {
      if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) {
        set.add(node.getId());
      }
    }

    // traverse outputs and recursive children as well
    List<ProcessorDefinition<?>> children = node.getOutputs();
    if (children != null && !children.isEmpty()) {
      for (ProcessorDefinition<?> child : children) {
        // traverse children also
        gatherAllNodeIds(child, set, onlyCustomId, includeAbstract);
      }
    }

    return set;
  }
 /**
  * Is there any outputs in the given list.
  *
  * <p>Is used for check if the route output has any real outputs (non abstracts)
  *
  * @param outputs the outputs
  * @param excludeAbstract whether or not to exclude abstract outputs (e.g. skip onException etc.)
  * @return <tt>true</tt> if has outputs, otherwise <tt>false</tt> is returned
  */
 @SuppressWarnings("unchecked")
 public static boolean hasOutputs(List<ProcessorDefinition> outputs, boolean excludeAbstract) {
   if (outputs == null || outputs.isEmpty()) {
     return false;
   }
   if (!excludeAbstract) {
     return !outputs.isEmpty();
   }
   for (ProcessorDefinition output : outputs) {
     if (output instanceof TransactedDefinition || output instanceof PolicyDefinition) {
       // special for those as they wrap entire output, so we should just check its output
       return hasOutputs(output.getOutputs(), excludeAbstract);
     }
     if (!output.isAbstract()) {
       return true;
     }
   }
   return false;
 }