/**
   * Takes the results of running linkWalkFirstPhase and creates an m/r job from them
   *
   * @param firstPhaseResult the results of running linkWalkfirstPhase.
   * @return the results from the intermediate bkeys of phase one.
   * @throws IOException
   */
  private MapReduceResult linkWalkSecondPhase(final MapReduceResult firstPhaseResult)
      throws IOException {
    try {
      @SuppressWarnings("rawtypes")
      Collection<LinkedList> bkeys = firstPhaseResult.getResult(LinkedList.class);

      BucketKeyMapReduce mr = new BucketKeyMapReduce(this);
      int stepCnt = 0;

      for (LinkedList<List<String>> step : bkeys) {
        // TODO find a way to *enforce* order here (custom
        // deserializer?)
        stepCnt++;
        for (List<String> input : step) {
          // use the step count as key data so we can aggregate the
          // results into the correct steps when they come back
          mr.addInput(input.get(0), input.get(1), Integer.toString(stepCnt));
        }
      }

      mr.addReducePhase(new NamedErlangFunction("riak_kv_mapreduce", "reduce_set_union"), false);
      mr.addMapPhase(
          new JSSourceFunction("function(v, keyData) { return [{\"step\": keyData, \"v\": v}]; }"),
          true);

      return mr.execute();
    } catch (ConversionException e) {
      throw new IOException(e.getMessage());
    } catch (RiakException e) {
      throw (IOException) e.getCause();
    }
  }
  /**
   * Creates an m/r job from the supplied link spec and executes it
   *
   * @param linkWalkSpec the Link Walk spec
   * @return {@link MapReduceResult} containing the end of the link and any intermediate bkeys for a
   *     second pass
   * @throws IOException
   */
  private MapReduceResult linkWalkFirstPhase(final LinkWalkSpec linkWalkSpec) throws IOException {
    BucketKeyMapReduce mr = new BucketKeyMapReduce(this);
    mr.addInput(linkWalkSpec.getStartBucket(), linkWalkSpec.getStartKey());
    int size = linkWalkSpec.size();
    int cnt = 0;

    for (LinkWalkStep step : linkWalkSpec) {
      cnt++;
      boolean keep = linkAccumulateToLinkPhaseKeep(step.getKeep(), cnt == size);
      mr.addLinkPhase(step.getBucket(), step.getTag(), keep);
    }

    // this is a bit of a hack. The low level API is using the high level
    // API so must strip out the exception.
    try {
      return mr.execute();
    } catch (RiakException e) {
      throw (IOException) e.getCause();
    }
  }