public Result update(String nodeId, String inputId, String extractorId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_EDIT, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    try {
      final Node node = nodeService.loadNode(nodeId);
      final Input input = node.getInput(inputId);
      final Extractor originalExtractor = extractorService.load(node, input, extractorId);
      final Map<String, String[]> form = request().body().asFormUrlEncoded();
      final String title = form.get("title")[0];

      CreateExtractorRequest request;
      try {
        request = this.generateCreateExtractorRequest(form, originalExtractor);
      } catch (NullPointerException e) {
        Logger.error("Cannot build extractor configuration.", e);
        return badRequest();
      }

      extractorService.update(extractorId, node, input, request);
      flash("success", "Extractor \"" + title + "\" was updated successfully");
      return redirect(controllers.routes.ExtractorsController.manage(nodeId, inputId));
    } catch (IOException e) {
      return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
    } catch (APIException e) {
      String message =
          "Could not update extractor! We expected HTTP 200, but got a HTTP "
              + e.getHttpCode()
              + ".";
      return status(500, views.html.errors.error.render(message, e, request()));
    } catch (NodeService.NodeNotFoundException e) {
      return status(
          404, views.html.errors.error.render(ApiClient.ERROR_MSG_NODE_NOT_FOUND, e, request()));
    }
  }
  public Result manageGlobal(String inputId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_EDIT, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    try {
      Node node = nodeService.loadMasterNode();
      Input input = node.getInput(inputId);

      return ok(
          views.html.system.inputs.extractors.manage.render(
              currentUser(),
              standardBreadcrumbs(node, input),
              node,
              input,
              extractorService.all(node, input)));
    } catch (IOException e) {
      return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
    } catch (APIException e) {
      String message =
          "Could not fetch system information. We expected HTTP 200, but got a HTTP "
              + e.getHttpCode()
              + ".";
      return status(500, views.html.errors.error.render(message, e, request()));
    }
  }
  public Result delete(String nodeId, String inputId, String extractorId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_EDIT, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    try {
      Node node = nodeService.loadNode(nodeId);
      extractorService.delete(node, node.getInput(inputId), extractorId);

      return redirect(controllers.routes.ExtractorsController.manage(nodeId, inputId));
    } catch (IOException e) {
      return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
    } catch (APIException e) {
      String message =
          "Could not delete extractor! We expected HTTP 204, but got a HTTP "
              + e.getHttpCode()
              + ".";
      return status(500, views.html.errors.error.render(message, e, request()));
    } catch (NodeService.NodeNotFoundException e) {
      return status(
          404, views.html.errors.error.render(ApiClient.ERROR_MSG_NODE_NOT_FOUND, e, request()));
    }
  }
  public Result exportExtractors(String nodeId, String inputId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_READ, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    try {
      Node node = nodeService.loadNode(nodeId);
      Input input = node.getInput(inputId);

      BreadcrumbList bc = standardBreadcrumbs(node, input);
      bc.addCrumb(
          "Export", controllers.routes.ExtractorsController.exportExtractors(nodeId, inputId));

      Map<String, Object> result = Maps.newHashMap();
      List<Map<String, Object>> extractors = Lists.newArrayList();
      for (Extractor extractor : extractorService.all(node, input)) {
        extractors.add(extractor.export());
      }
      result.put("extractors", extractors);
      result.put("version", Version.VERSION.toString());

      String extractorExport = Json.toJsonString(result);

      return ok(
          views.html.system.inputs.extractors.export.render(
              currentUser(), bc, node, input, extractorExport));
    } catch (IOException e) {
      return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
    } catch (APIException e) {
      String message =
          "Could not fetch system information. We expected HTTP 200, but got a HTTP "
              + e.getHttpCode()
              + ".";
      return status(500, views.html.errors.error.render(message, e, request()));
    } catch (NodeService.NodeNotFoundException e) {
      return status(
          404, views.html.errors.error.render(ApiClient.ERROR_MSG_NODE_NOT_FOUND, e, request()));
    }
  }
  public Result editExtractor(String nodeId, String inputId, String extractorId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_EDIT, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    try {
      final Node node = nodeService.loadNode(nodeId);
      final Input input = node.getInput(inputId);
      final Extractor extractor = extractorService.load(node, input, extractorId);
      final String sourceField = extractor.getSourceField();
      String example;
      try {
        final MessageResult exampleMessage = input.getRecentlyReceivedMessage(nodeId);
        example = exampleMessage.getFields().get(sourceField).toString();
      } catch (Exception e) {
        example = null;
      }

      return ok(
          views.html.system.inputs.extractors.edit_extractor.render(
              currentUser(),
              standardBreadcrumbs(node, input, extractor),
              node,
              input,
              extractor,
              example));
    } catch (IOException e) {
      return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
    } catch (APIException e) {
      String message =
          "Could not fetch system information. We expected HTTP 200, but got a HTTP "
              + e.getHttpCode()
              + ".";
      return status(500, views.html.errors.error.render(message, e, request()));
    } catch (NodeService.NodeNotFoundException e) {
      return status(
          404, views.html.errors.error.render(ApiClient.ERROR_MSG_NODE_NOT_FOUND, e, request()));
    }
  }
  public Result importExtractors(String nodeId, String inputId) {
    if (!Permissions.isPermitted(RestPermissions.INPUTS_EDIT, inputId)) {
      return redirect(routes.StartpageController.redirect());
    }
    Map<String, String> form = flattenFormUrlEncoded(request().body().asFormUrlEncoded());

    if (!form.containsKey("extractors") || form.get("extractors").isEmpty()) {
      flash("error", "No JSON provided. Please fill out the import definition field.");
      return redirect(
          controllers.routes.ExtractorsController.importExtractorsPage(nodeId, inputId));
    }

    ExtractorListImportRequest elir;
    try {
      elir = Json.fromJson(Json.parse(form.get("extractors")), ExtractorListImportRequest.class);
    } catch (Exception e) {
      Logger.error("Could not read JSON.", e);
      flash("error", "Could not read JSON.");
      return redirect(
          controllers.routes.ExtractorsController.importExtractorsPage(nodeId, inputId));
    }

    /*
     * For future versions with breaking changes: check the "version" field in the ExtractorListImportRequest.
     *
     * Thank me later.
     */

    int successes = 0;
    List<String> failedExtractors = new ArrayList<>();
    for (ExtractorImportRequest importRequest : elir.extractors) {
      try {
        Node node = nodeService.loadNode(nodeId);

        Extractor.Type type = Extractor.Type.valueOf(importRequest.extractorType.toUpperCase());

        Extractor extractor =
            extractorFactory.forCreate(
                Extractor.CursorStrategy.valueOf(importRequest.cursorStrategy.toUpperCase()),
                importRequest.title,
                importRequest.sourceField,
                importRequest.targetField,
                type,
                currentUser(),
                Extractor.ConditionType.valueOf(importRequest.conditionType.toUpperCase()),
                importRequest.conditionValue);

        extractor.loadConfigFromImport(type, importRequest.extractorConfig);
        extractor.loadConvertersFromImport(importRequest.converters);
        extractor.setOrder(importRequest.order);
        extractorService.create(node, node.getInput(inputId), extractor.toCreateExtractorRequest());
        successes++;
      } catch (Exception e) {
        failedExtractors.add(importRequest.title);
        Logger.error(
            "Could not import extractor \"" + importRequest.title + "\": " + e.getMessage());
        Logger.debug("Details for failing to import extractor \"" + importRequest.title + "\":", e);
      }
    }

    if (!failedExtractors.isEmpty()) {
      flash(
          "error",
          "Failed to import "
              + failedExtractors.size()
              + " extractors: "
              + Joiner.on(',').useForNull("[null title]").join(failedExtractors));
    }

    flash(
        "success",
        "Successfully imported " + successes + " of " + elir.extractors.size() + " extractors.");
    return redirect(controllers.routes.ExtractorsController.manage(nodeId, inputId));
  }