/**
   * List the items on the dispatcher queue for a project
   *
   * @param project Project name
   * @return Collection of Strings listing the active dispatcher queue items
   * @throws CentralDispatcherException if an error occurs
   */
  public Collection<QueuedItem> listDispatcherQueue(final String project)
      throws CentralDispatcherException {
    if (null == project) {
      throw new CentralDispatcherException(
          "Unsupported operation: project is required by the RunDeck API");
    }
    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("project", project);

    final WebserviceResponse response;
    try {
      response =
          serverService.makeRundeckRequest(RUNDECK_API_LIST_EXECUTIONS_PATH, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }

    validateResponse(response);

    ////////////////////
    // parse result list of queued items, return the collection of QueuedItems
    ///////////////////

    return parseExecutionListResult(response);
  }
  public QueuedItemResult queueDispatcherJob(final IDispatchedJob dispatchedJob)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    if (null == dispatchedJob.getJobRef()) {
      throw new IllegalArgumentException("JobRef was null");
    }
    if (null != dispatchedJob.getJobRef().getJobId()) {
      params.put("id", dispatchedJob.getJobRef().getJobId());
    } else {
      if (null != dispatchedJob.getJobRef().getName()) {
        params.put("jobName", dispatchedJob.getJobRef().getName());
      }
      if (null != dispatchedJob.getJobRef().getGroup()) {
        params.put("groupPath", dispatchedJob.getJobRef().getGroup());
      }
    }

    addNodeSetParams(params, dispatchedJob.getNodeSet(), dispatchedJob.isKeepgoing(), "extra.");
    addLoglevelParams(params, dispatchedJob.getLoglevel(), "extra.");
    if (null != dispatchedJob.getArgs() && dispatchedJob.getArgs().length > 0) {
      params.put("extra.argString", CLIUtils.generateArgline(null, dispatchedJob.getArgs()));
    }
    /*
     * submit request to the URL path, expecting standard "queued item list" result
     */
    return submitQueueRequest(null, params, RUNDECK_JOBS_RUN);
  }
  public DispatcherResult killDispatcherExecution(final String execId)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("id", execId);
    params.put("xmlreq", "true");

    // 2. send request via ServerService
    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(RUNDECK_KILL_JOB_PATH, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }

    final Envelope envelope = validateResponse(response);

    final boolean result = envelope.isSuccessResult();
    final StringBuffer sb = envelope.successMessages();
    return new DispatcherResult() {
      public boolean isSuccessful() {
        return result;
      }

      public String getMessage() {
        return sb.toString();
      }
    };
  }
  /**
   * Submit a request to the server which expects an execution id in response, and return a single
   * QueuedItemResult parsed from the response.
   *
   * @param tempxml xml temp file (or null)
   * @param otherparams parameters for the request
   * @param requestPath
   * @return a single QueuedItemResult
   * @throws com.dtolabs.rundeck.core.dispatcher.CentralDispatcherException if an error occurs
   */
  private QueuedItemResult submitRunRequest(
      final File tempxml, final HashMap<String, String> otherparams, final String requestPath)
      throws CentralDispatcherException {

    final HashMap<String, String> params = new HashMap<String, String>();
    if (null != otherparams) {
      params.putAll(otherparams);
    }

    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(requestPath, params, tempxml, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    validateResponse(response);

    final Document resultDoc = response.getResultDoc();

    if (null != resultDoc.selectSingleNode("/result/execution")
        && null != resultDoc.selectSingleNode("/result/execution/@id")) {
      final Node node = resultDoc.selectSingleNode("/result/execution/@id");
      final String succeededId = node.getStringValue();
      final String name = "adhoc";
      String url = createExecutionURL(succeededId);
      url = makeAbsoluteURL(url);
      logger.info("\t[" + succeededId + "] <" + url + ">");
      return QueuedItemResultImpl.successful("Succeeded queueing " + name, succeededId, url, name);
    }
    return QueuedItemResultImpl.failed("Server response contained no success information.");
  }
  /**
   * Submit a request to the server which expects a list of execution items in the response, and
   * return a single QueuedItemResult parsed from the response.
   *
   * @param tempxml xml temp file (or null)
   * @param otherparams parameters for the request
   * @param requestPath
   * @return a single QueuedItemResult
   * @throws com.dtolabs.rundeck.core.dispatcher.CentralDispatcherException if an error occurs
   */
  private QueuedItemResult submitExecutionRequest(
      final File tempxml, final HashMap<String, String> otherparams, final String requestPath)
      throws CentralDispatcherException {

    final HashMap<String, String> params = new HashMap<String, String>();
    if (null != otherparams) {
      params.putAll(otherparams);
    }

    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(requestPath, params, tempxml, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    validateResponse(response);

    final ArrayList<QueuedItem> list = parseExecutionListResult(response);
    if (null == list || list.size() < 1) {

      return QueuedItemResultImpl.failed("Server response contained no success information.");
    } else {

      final QueuedItem next = list.iterator().next();
      return QueuedItemResultImpl.successful(
          "Succeeded queueing " + next.getName(), next.getId(), next.getUrl(), next.getName());
    }
  }
  public Collection<IStoredJob> reallistStoredJobs(final IStoredJobsQuery iStoredJobsQuery)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    final String nameMatch = iStoredJobsQuery.getNameMatch();
    String groupMatch = iStoredJobsQuery.getGroupMatch();
    final String projectFilter = iStoredJobsQuery.getProjectFilter();
    final String idlistFilter = iStoredJobsQuery.getIdlist();

    if (null != nameMatch) {
      params.put("jobExactFilter", nameMatch);
    }
    if (null != groupMatch) {
      final Matcher matcher = Pattern.compile("^/*(.+?)/*$").matcher(groupMatch);
      if (matcher.matches()) {
        // strip leading and trailing slashes
        groupMatch = matcher.group(1);
      }
      params.put("groupPathExact", groupMatch);
    }
    if (null != projectFilter) {
      params.put("project", projectFilter);
    }
    if (null != idlistFilter) {
      params.put("idlist", idlistFilter);
    }

    // 2. send request via ServerService
    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(RUNDECK_API_JOBS_LIST_PATH, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    validateResponse(response);
    // extract job list
    final Document resultDoc = response.getResultDoc();
    final ArrayList<IStoredJob> list = new ArrayList<IStoredJob>();

    final Node jobs = resultDoc.selectSingleNode("/result/jobs");
    for (final Object job1 : jobs.selectNodes("job")) {
      final Node job = (Node) job1;
      final String id = job.selectSingleNode("@id").getStringValue();
      final String name = job.selectSingleNode("name").getStringValue();
      final String group = job.selectSingleNode("group").getStringValue();
      final String desc = job.selectSingleNode("description").getStringValue();
      final String url = createJobURL(id);
      list.add(StoredJobImpl.create(id, name, url, group, desc, projectFilter));
    }

    return list;
  }
  public static void addNodeSetParams(
      final HashMap<String, String> params,
      final NodeSet nodeSet,
      final Boolean isKeepgoing,
      final String prefix) {

    if (null == nodeSet) {
      return;
    }
    if (nodeSet.getThreadCount() > 0) {
      params.put(prefix + "nodeThreadcount", Integer.toString(nodeSet.getThreadCount()));
    }
    if (null != isKeepgoing) {
      params.put(prefix + "nodeKeepgoing", Boolean.toString(isKeepgoing));
    }
    if (nodeSet.isBlank()) {
      return;
    }
    params.put(prefix + "doNodedispatch", "true");
    boolean excludeprecedence = true;
    if (null != nodeSet.getExclude() && nodeSet.getExclude().isDominant()) {
      excludeprecedence = true;
    } else if (null != nodeSet.getInclude() && nodeSet.getInclude().isDominant()) {
      excludeprecedence = false;
    }
    params.put(prefix + "nodeExcludePrecedence", Boolean.toString(excludeprecedence));

    final NodeSet.Include include = nodeSet.getInclude();

    for (NodeSet.FILTER_ENUM filter : NodeSet.FILTER_ENUM.values()) {
      String value = null;
      if (null != include && !include.isBlank()) {
        value = filter.value(include);
      }

      String key = nodeFilterParams.get(filter.getName());
      if (null != value && !"".equals(value)) {
        params.put(prefix + "nodeInclude" + key, value);
      } else {
        params.put(prefix + "nodeInclude" + key, "");
      }
    }
    final NodeSet.Exclude exclude = nodeSet.getExclude();

    for (NodeSet.FILTER_ENUM filter : NodeSet.FILTER_ENUM.values()) {
      String value = null;
      if (null != exclude && !exclude.isBlank()) {
        value = filter.value(exclude);
      }
      String key = nodeFilterParams.get(filter.getName());
      if (null != value && !"".equals(value)) {
        params.put(prefix + "nodeExclude" + key, value);
      } else {
        params.put(prefix + "nodeExclude" + key, "");
      }
    }
  }
  public Collection<QueuedItem> listDispatcherQueue() throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("xmlreq", "true");

    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(RUNDECK_LIST_EXECUTIONS_PATH, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }

    validateResponse(response);

    ////////////////////
    // parse result list of queued items, return the collection of QueuedItems
    ///////////////////

    final Document resultDoc = response.getResultDoc();

    final Node node = resultDoc.selectSingleNode("/result/items");
    final List items = node.selectNodes("item");
    final ArrayList<QueuedItem> list = new ArrayList<QueuedItem>();
    if (null != items && items.size() > 0) {
      for (final Object o : items) {
        final Node node1 = (Node) o;
        final String id = node1.selectSingleNode("id").getStringValue();
        final String name = node1.selectSingleNode("name").getStringValue();
        String url = node1.selectSingleNode("url").getStringValue();
        url = makeAbsoluteURL(url);
        logger.info("\t" + ": " + name + " [" + id + "] <" + url + ">");
        list.add(QueuedItemResultImpl.createQueuedItem(id, url, name));
      }
    }

    return list;
  }
  public Collection<IStoredJobLoadResult> loadJobs(ILoadJobsRequest iLoadJobsRequest, File input)
      throws CentralDispatcherException {
    final HashMap params = new HashMap();
    params.put("dupeOption", iLoadJobsRequest.getDuplicateOption().toString());
    params.put("xmlreq", "true");

    /*
     * Send the request bean and the file as a multipart request.
     */

    // 2. send request via ServerService
    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(RUNDECK_JOBS_UPLOAD, params, input, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    validateResponse(response);

    ////////////////////
    // parse result list of queued items, return the collection of QueuedItems
    ///////////////////

    final Document result = response.getResultDoc();

    final int succeeded;
    final int failed;
    final int skipped;
    Node node = result.selectSingleNode("/result/succeeded/@count");
    if (null != node) {
      succeeded = Integer.parseInt(node.getStringValue());
    } else {
      succeeded = -1;
    }
    node = result.selectSingleNode("/result/failed/@count");
    if (null != node) {
      failed = Integer.parseInt(node.getStringValue());
    } else {
      failed = -1;
    }
    node = result.selectSingleNode("/result/skipped/@count");
    if (null != node) {
      skipped = Integer.parseInt(node.getStringValue());
    } else {
      skipped = -1;
    }
    ArrayList<IStoredJobLoadResult> resultList = new ArrayList<IStoredJobLoadResult>();
    if (succeeded > 0) {
      logger.debug("Succeeded creating/updating " + succeeded + " Jobs:");
      final List nodes = result.selectNodes("/result/succeeded/job");
      for (final Object node2 : nodes) {
        final Node node1 = (Node) node2;
        final IStoredJobLoadResult storedJobLoadResult =
            parseStoredJobResult(node1, true, false, "Succeeded");
        resultList.add(storedJobLoadResult);
      }
    }
    if (failed > 0) {
      logger.debug("Failed to add " + failed + " Jobs:");
      final List nodes = result.selectNodes("/result/failed/job");
      for (final Object node2 : nodes) {
        final Node node1 = (Node) node2;
        final String error =
            null != node1.selectSingleNode("error")
                ? node1.selectSingleNode("error").getStringValue()
                : "Failed";
        final IStoredJobLoadResult storedJobLoadResult =
            parseStoredJobResult(node1, false, false, error);

        resultList.add(storedJobLoadResult);
      }
    }
    if (skipped > 0) {
      logger.debug("Skipped " + skipped + " Jobs:");
      final List nodes = result.selectNodes("/result/skipped/job");
      for (final Object node2 : nodes) {
        final Node node1 = (Node) node2;

        final String error =
            null != node1.selectSingleNode("error")
                ? node1.selectSingleNode("error").getStringValue()
                : "Skipped";
        final IStoredJobLoadResult storedJobLoadResult =
            parseStoredJobResult(node1, true, true, error);
        resultList.add(storedJobLoadResult);
      }
    }
    return resultList;
  }
  public Collection<IStoredJob> listStoredJobs(
      final IStoredJobsQuery iStoredJobsQuery, final OutputStream output)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("xmlreq", "true");
    final String nameMatch = iStoredJobsQuery.getNameMatch();
    String groupMatch = iStoredJobsQuery.getGroupMatch();
    final String projectFilter = iStoredJobsQuery.getProjectFilter();
    final String commandFilter = iStoredJobsQuery.getCommand();
    final String idlistFilter = iStoredJobsQuery.getIdlist();
    final String typeFilter = iStoredJobsQuery.getType();
    final String resourceFilter = iStoredJobsQuery.getResource();

    if (null != nameMatch) {
      params.put("jobFilter", nameMatch);
    }
    if (null != groupMatch) {
      final Matcher matcher = Pattern.compile("^/*(.+?)/*$").matcher(groupMatch);
      if (matcher.matches()) {
        // strip leading and trailing slashes
        groupMatch = matcher.group(1);
      }
      params.put("groupPath", groupMatch);
    }
    if (null != projectFilter) {
      params.put("projFilter", projectFilter);
    }
    if (null != resourceFilter) {
      params.put("objFilter", resourceFilter);
    }
    if (null != typeFilter) {
      params.put("typeFilter", typeFilter);
    }
    if (null != commandFilter) {
      params.put("cmdFilter", commandFilter);
    }
    if (null != idlistFilter) {
      params.put("idlist", idlistFilter);
    }

    params.put("xmlreq", "true");

    // 2. send request via ServerService
    final WebserviceResponse response;
    try {
      response =
          serverService.makeRundeckRequest(RUNDECK_LIST_STORED_JOBS_PATH, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }

    validateJobsResponse(response);

    ////////////////////
    // parse result list of queued items, return the collection of QueuedItems
    ///////////////////

    final Document resultDoc = response.getResultDoc();

    final Node node = resultDoc.selectSingleNode("/joblist");
    final ArrayList<IStoredJob> list = new ArrayList<IStoredJob>();
    if (null == node) {
      return list;
    }
    final List items = node.selectNodes("job");
    if (null != items && items.size() > 0) {
      for (final Object o : items) {
        final Node node1 = (Node) o;
        final String id = node1.selectSingleNode("id").getStringValue();
        final String name = node1.selectSingleNode("name").getStringValue();
        final String url = createJobURL(id);

        final Node gnode = node1.selectSingleNode("group");
        final String group = null != gnode ? gnode.getStringValue() : null;
        final String description = node1.selectSingleNode("description").getStringValue();
        list.add(StoredJobImpl.create(id, name, url, group, description));
      }
    }

    if (null != output) {
      // write output doc to the outputstream
      final OutputFormat format = OutputFormat.createPrettyPrint();
      try {
        final XMLWriter writer = new XMLWriter(output, format);
        writer.write(resultDoc);
        writer.flush();
      } catch (IOException e) {
        throw new CentralDispatcherServerRequestException(e);
      }
    }

    return list;
  }
  /**
   * Submit a request to the server which expects a list of queued item results in the response, and
   * return a single QueuedItemResult parsed from the response.
   *
   * @param tempxml xml temp file (or null)
   * @param otherparams parameters for the request
   * @param requestPath
   * @return a single QueuedItemResult
   * @throws CentralDispatcherException if an error occurs
   */
  private QueuedItemResult submitQueueRequest(
      final File tempxml, final HashMap<String, String> otherparams, final String requestPath)
      throws CentralDispatcherException {

    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("xmlreq", "true");
    if (null != otherparams) {
      params.putAll(otherparams);
    }

    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(requestPath, params, tempxml, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    validateResponse(response);

    ////////////////////
    // parse result list of execution responses.  this implementation handles multiple responses,
    // but only
    // returns a single QueuedItem (the first one found).
    // TODO: update to return multiple QueuedItems when multiple job queue requests are supported
    ///////////////////

    final Document resultDoc = response.getResultDoc();

    final int succeeded;
    final int failed;

    Node node = resultDoc.selectSingleNode("/result/succeeded/@count");
    if (null != node) {
      succeeded = Integer.parseInt(node.getStringValue());
    } else {
      succeeded = -1;
    }
    node = resultDoc.selectSingleNode("/result/failed/@count");
    if (null != node) {
      failed = Integer.parseInt(node.getStringValue());
    } else {
      failed = -1;
    }
    final String succeededId;
    if (succeeded > 0) {
      logger.info("Succeeded queueing " + succeeded + " Job" + (succeeded > 1 ? "s" : "") + ":");
      final List nodes = resultDoc.selectNodes("/result/succeeded/execution");
      final Node node1 = (Node) nodes.iterator().next();
      final String index = node1.selectSingleNode("@index").getStringValue();
      final String id = node1.selectSingleNode("id").getStringValue();
      succeededId = id;
      final String name = node1.selectSingleNode("name").getStringValue();
      String url = node1.selectSingleNode("url").getStringValue();
      url = makeAbsoluteURL(url);
      logger.info("\t" + index + ": " + name + " [" + id + "] <" + url + ">");
      return QueuedItemResultImpl.successful("Succeeded queueing " + name, succeededId, url, name);
    }
    if (failed > 0) {
      final String s1 = "Failed to queue " + failed + " Job" + (failed > 1 ? "s" : "") + ":";
      logger.error(s1);
      final List nodes = resultDoc.selectNodes("/result/failed/execution");
      final Node node1 = (Node) nodes.iterator().next();
      final String index = node1.selectSingleNode("@index").getStringValue();
      final String id =
          null != node1.selectSingleNode("id")
              ? node1.selectSingleNode("id").getStringValue()
              : null;
      String url =
          null != node1.selectSingleNode("url")
              ? node1.selectSingleNode("url").getStringValue()
              : null;
      url = makeAbsoluteURL(url);
      final String error =
          null != node1.selectSingleNode("error")
              ? node1.selectSingleNode("error").getStringValue()
              : null;
      final String message =
          null != node1.selectSingleNode("message")
              ? node1.selectSingleNode("message").getStringValue()
              : null;
      final String errmsg =
          error
              + (null != id ? " [" + id + "] <" + url + ">" : "")
              + (null != message ? " : " + message : "");
      final String s2 = index + ": " + errmsg;
      logger.error(s2);
      return QueuedItemResultImpl.failed(errmsg);
    }
    return QueuedItemResultImpl.failed("Server response contained no success information.");
  }
  public QueuedItemResult queueDispatcherJob(final IDispatchedJob dispatchedJob)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    if (null == dispatchedJob.getJobRef()) {
      throw new IllegalArgumentException("JobRef was null");
    }
    final String jobid;
    if (null != dispatchedJob.getJobRef().getJobId()) {
      jobid = dispatchedJob.getJobRef().getJobId();
    } else {
      if (null == dispatchedJob.getJobRef().getName()
          || "".equals(dispatchedJob.getJobRef().getName())) {
        throw new CentralDispatcherException("job name input is required");
      }
      final String project = dispatchedJob.getJobRef().getProject();
      final String name = dispatchedJob.getJobRef().getName();
      final String group;
      if (null != dispatchedJob.getJobRef().getGroup()
          && !"".equals(dispatchedJob.getJobRef().getGroup())) {
        group = dispatchedJob.getJobRef().getGroup();
      } else {
        // indicates a top level job
        group = "-";
      }

      // Query to find matching job
      final Collection<IStoredJob> iStoredJobs =
          reallistStoredJobs(new IStoredJobsQueryImpl(name, group, null, project));
      if (null == iStoredJobs) {
        throw new CentralDispatcherException("Unable to query jobs");
      }
      if (iStoredJobs.size() < 1) {
        throw new CentralDispatcherException(
            "Job not found matching query: " + (null != group ? group : "") + "/" + name);
      }
      if (iStoredJobs.size() > 1) {
        ArrayList<String> ids = new ArrayList<String>();
        for (final IStoredJob iStoredJob : iStoredJobs) {
          ids.add(iStoredJob.getJobId());
        }
        throw new CentralDispatcherException(
            "Job was not unique: "
                + (null != group ? group : "")
                + "/"
                + name
                + ": "
                + iStoredJobs.size()
                + " jobs found: "
                + ids);
      }
      // use found id
      final IStoredJob next = iStoredJobs.iterator().next();
      jobid = next.getJobId();
    }

    addAPINodeSetParams(params, dispatchedJob.getNodeSet(), dispatchedJob.isKeepgoing());
    addLoglevelParams(params, dispatchedJob.getLoglevel());
    if (null != dispatchedJob.getArgs() && dispatchedJob.getArgs().length > 0) {
      params.put("argString", CLIUtils.generateArgline(null, dispatchedJob.getArgs()));
    }

    final String apipath = substitutePathVariable(RUNDECK_API_JOBS_RUN, "id", jobid);
    return submitExecutionRequest(null, params, apipath);
  }
  public Collection<IStoredJob> listStoredJobs(
      final IStoredJobsQuery iStoredJobsQuery,
      final OutputStream output,
      final JobDefinitionFileFormat fformat)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    final String nameMatch = iStoredJobsQuery.getNameMatch();
    String groupMatch = iStoredJobsQuery.getGroupMatch();
    final String projectFilter = iStoredJobsQuery.getProjectFilter();
    final String idlistFilter = iStoredJobsQuery.getIdlist();

    final String expectedContentType;
    if (null != fformat) {
      params.put("format", fformat.getName());
      expectedContentType = fformat == JobDefinitionFileFormat.xml ? "text/xml" : "text/yaml";
    } else {
      params.put("format", JobDefinitionFileFormat.xml.getName());
      expectedContentType = "text/xml";
    }
    if (null != nameMatch) {
      params.put("jobFilter", nameMatch);
    }
    if (null != groupMatch) {
      final Matcher matcher = Pattern.compile("^/*(.+?)/*$").matcher(groupMatch);
      if (matcher.matches()) {
        // strip leading and trailing slashes
        groupMatch = matcher.group(1);
      }
      params.put("groupPath", groupMatch);
    }
    if (null != projectFilter) {
      params.put("project", projectFilter);
    }
    if (null != idlistFilter) {
      params.put("idlist", idlistFilter);
    }

    // 2. send request via ServerService
    final WebserviceResponse response;
    try {
      response =
          serverService.makeRundeckRequest(
              RUNDECK_API_JOBS_EXPORT_PATH, params, null, null, expectedContentType);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }
    checkErrorResponse(response);
    // if xml, do local validation and listing
    if (null == fformat || fformat == JobDefinitionFileFormat.xml) {
      validateJobsResponse(response);

      ////////////////////
      // parse result list of queued items, return the collection of QueuedItems
      ///////////////////

      final Document resultDoc = response.getResultDoc();

      final Node node = resultDoc.selectSingleNode("/joblist");
      final ArrayList<IStoredJob> list = new ArrayList<IStoredJob>();
      if (null == node) {
        return list;
      }
      final List items = node.selectNodes("job");
      if (null != items && items.size() > 0) {
        for (final Object o : items) {
          final Node node1 = (Node) o;
          final Node uuid = node1.selectSingleNode("uuid");
          final Node id1 = node1.selectSingleNode("id");
          final String id = null != uuid ? uuid.getStringValue() : id1.getStringValue();
          final String name = node1.selectSingleNode("name").getStringValue();
          final String url = createJobURL(id);

          final Node gnode = node1.selectSingleNode("group");
          final String group = null != gnode ? gnode.getStringValue() : null;
          final String description = node1.selectSingleNode("description").getStringValue();
          list.add(StoredJobImpl.create(id, name, url, group, description, projectFilter));
        }
      }

      if (null != output) {
        // write output doc to the outputstream
        final OutputFormat format = OutputFormat.createPrettyPrint();
        try {
          final XMLWriter writer = new XMLWriter(output, format);
          writer.write(resultDoc);
          writer.flush();
        } catch (IOException e) {
          throw new CentralDispatcherServerRequestException(e);
        }
      }
      return list;
    } else if (fformat == JobDefinitionFileFormat.yaml) {
      // do rought yaml parse
      final Collection<Map> mapCollection = validateJobsResponseYAML(response);
      final ArrayList<IStoredJob> list = new ArrayList<IStoredJob>();

      if (null == mapCollection || mapCollection.size() < 1) {
        return list;
      }
      for (final Map map : mapCollection) {
        final Object uuidobj = map.get("uuid");
        final Object idobj = map.get("id");
        final String id = null != uuidobj ? uuidobj.toString() : idobj.toString();
        final String name = (String) map.get("name");
        final String group = map.containsKey("group") ? (String) map.get("group") : null;
        final String desc = map.containsKey("description") ? (String) map.get("description") : "";
        final String url = createJobURL(id);
        list.add(StoredJobImpl.create(id, name, url, group, desc, projectFilter));
      }

      if (null != output) {
        // write output doc to the outputstream
        try {
          final Writer writer = new OutputStreamWriter(output);
          writer.write(response.getResults());
          writer.flush();
        } catch (IOException e) {
          throw new CentralDispatcherServerRequestException(e);
        }
      }
      return list;
    }
    return null;
  }
  public QueuedItemResult queueDispatcherScript(final IDispatchedScript iDispatchedScript)
      throws CentralDispatcherException {
    final String argString;
    final String scriptString;
    final boolean isExec;

    try {

      // write script to file
      final InputStream stream = iDispatchedScript.getScriptAsStream();
      if (null != iDispatchedScript.getScript() || null != stream) {
        // full script
        if (null != iDispatchedScript.getScript()) {
          scriptString = iDispatchedScript.getScript();
        } else {
          // read stream to string
          final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          Streams.copyStream(stream, byteArrayOutputStream);
          scriptString = new String(byteArrayOutputStream.toByteArray());
        }
        if (null != iDispatchedScript.getArgs() && iDispatchedScript.getArgs().length > 0) {
          argString = CLIUtils.generateArgline(null, iDispatchedScript.getArgs());
        } else {
          argString = null;
        }

        isExec = false;
      } else if (null != iDispatchedScript.getServerScriptFilePath()) {
        // server-local script filepath

        // read stream to string
        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Streams.copyStream(
            new FileInputStream(new File(iDispatchedScript.getServerScriptFilePath())),
            byteArrayOutputStream);
        scriptString = new String(byteArrayOutputStream.toByteArray());

        if (null != iDispatchedScript.getArgs() && iDispatchedScript.getArgs().length > 0) {
          argString = CLIUtils.generateArgline(null, iDispatchedScript.getArgs());
        } else {
          argString = null;
        }
        isExec = false;
      } else if (null != iDispatchedScript.getArgs() && iDispatchedScript.getArgs().length > 0) {
        // shell command
        scriptString = null;
        argString = CLIUtils.generateArgline(null, iDispatchedScript.getArgs());
        isExec = true;
      } else {
        throw new IllegalArgumentException(
            "Dispatched script did not specify a command, script or filepath");
      }

    } catch (IOException e) {
      throw new CentralDispatcherServerRequestException(
          "Unable to queue command: " + e.getMessage(), e);
    }

    // request parameters
    final HashMap<String, String> params = new HashMap<String, String>();

    params.put("project", iDispatchedScript.getFrameworkProject());
    if (isExec) {
      params.put("exec", argString);
    } else {
      params.put("scriptFile", scriptString);
    }
    addLoglevelParams(params, iDispatchedScript.getLoglevel());
    addAPINodeSetParams(
        params, iDispatchedScript.getNodeSet(), iDispatchedScript.getNodeSet().isKeepgoing());

    return submitRunRequest(
        null, params, isExec ? RUNDECK_API_RUN_COMMAND : RUNDECK_API_RUN_SCRIPT);
  }
  /**
   * Report execution status
   *
   * @param project project
   * @param title execution title
   * @param status result status, either 'succeed','cancel','fail'
   * @param failedNodeCount total node count
   * @param successNodeCount count of successful nodes
   * @param tags
   * @param script script content (can be null if summary specified)
   * @param summary summary of execution (can be null if script specified)
   * @param start start date (can be null)
   * @param end end date (can be null)
   * @throws com.dtolabs.rundeck.core.dispatcher.CentralDispatcherException
   */
  public void reportExecutionStatus(
      final String project,
      final String title,
      final String status,
      final int failedNodeCount,
      final int successNodeCount,
      final String tags,
      final String script,
      final String summary,
      final Date start,
      final Date end)
      throws CentralDispatcherException {
    final HashMap<String, String> params = new HashMap<String, String>();
    params.put("project", project);
    params.put("title", title);
    params.put("status", status);
    params.put("nodesuccesscount", Integer.toString(successNodeCount));
    params.put("nodefailcount", Integer.toString(failedNodeCount));
    if (null != tags) {
      params.put("tags", tags);
    }
    if (null != script) {
      params.put("script", script);
    }
    params.put("summary", summary);
    if (null != start) {
      params.put("start", Long.toString(start.getTime()));
    }
    if (null != end) {
      params.put("end", Long.toString(end.getTime()));
    }

    final WebserviceResponse response;
    try {
      response = serverService.makeRundeckRequest(RUNDECK_API_EXECUTION_REPORT, params, null, null);
    } catch (MalformedURLException e) {
      throw new CentralDispatcherServerRequestException("Failed to make request", e);
    }

    validateResponse(response);
  }