public void serveDescription(RestServiceRequest rsr) {
    rsr.startDoc();

    if (rsr.getResponseContentType() == ResponseType.HTML)
      rsr.write("<h1>Operation: ").write(getRelativeUrl()).write("</h1>");

    rsr.datawriter
        .object()
        .key("name")
        .value(getRelativeUrl())
        .key("description")
        .value(description)
        .key("url")
        .value(RestServices.getAbsoluteUrl(getRelativeUrl()))
        .key("arguments")
        .value(
            hasArgument ? JSONSchemaBuilder.build(Utils.getFirstArgumentType(microflowname)) : null)
        .key("accepts_binary_data")
        .value(isFileSource)
        .key("result")
        .value(
            isFileTarget
                ? RestServices.CONTENTTYPE_OCTET + " stream"
                : JSONSchemaBuilder.build(Core.getReturnType(microflowname)))
        .endObject();

    rsr.endDoc();
  }
  public static void serveServiceOverview(RestServiceRequest rsr) {
    rsr.startDoc();
    if (rsr.getResponseContentType() == ResponseType.HTML) rsr.write("<h1>RestServices</h1>");

    rsr.datawriter.object().key("RestServices").value(RestServices.VERSION).key("services").array();

    for (String service : RestServiceHandler.getServiceBaseUrls())
      rsr.datawriter.value(RestServices.getAbsoluteUrl(service) + "?" + RestServices.PARAM_ABOUT);

    rsr.datawriter.endArray().endObject();

    rsr.endDoc();
  }
 private void addEndpointParam(String param, Object description) {
   if (isHTML) {
     rsr.write("<tr><td>" + param + "</td><td>");
     rsr.datawriter.value(description);
     rsr.write("</td></tr>");
   } else
     rsr.datawriter
         .object()
         .key("name")
         .value(param)
         .key("description")
         .value(description)
         .endObject();
 }
  @Override
  public void execute(final RestServiceRequest rsr, Map<String, String> params) throws Exception {
    if (params.containsKey(RestServices.PARAM_ABOUT)) {
      serveDescription(rsr);
    } else {
      Map<String, Object> args = new HashMap<String, Object>();
      IMendixObject inputObject = parseInputData(rsr, params);

      if (inputObject != null) args.put(argName, inputObject);

      if (isReturnTypePrimitive)
        rsr.setResponseContentType(
            ResponseType.PLAIN); // default, but might be overriden by the executing mf
      else if (isFileTarget) rsr.setResponseContentType(ResponseType.BINARY);

      Object result = Core.execute(rsr.getContext(), microflowname, args);
      writeOutputData(rsr, result);
    }
  }
  private IMendixObject parseInputData(RestServiceRequest rsr, Map<String, String> params)
      throws IOException, ServletException, Exception {
    if (!hasArgument) return null;

    if (!Utils.hasDataAccess(Core.getMetaObject(argType), rsr.getContext()))
      throw new IllegalStateException(
          "Cannot instantiate object of type '"
              + argType
              + "', the object is not accessiable for users with role "
              + rsr.getContext().getSession().getUserRolesNames()
              + ". Please check the access rules");

    IMendixObject argObject = Core.instantiate(rsr.getContext(), argType);
    JSONObject data = new JSONObject();

    // multipart data
    if (rsr.getRequestContentType() == RequestContentType.MULTIPART) {
      parseMultipartData(rsr, argObject, data);
    }

    // json data
    else if (rsr.getRequestContentType() == RequestContentType.JSON
        || (rsr.getRequestContentType() == RequestContentType.OTHER && !isFileSource)) {
      String body = IOUtils.toString(rsr.request.getInputStream());
      data = new JSONObject(StringUtils.isEmpty(body) ? "{}" : body);
    }

    // not multipart but expecting a file?
    else if (isFileSource) {
      Core.storeFileDocumentContent(rsr.getContext(), argObject, rsr.request.getInputStream());
    }

    RestServiceHandler.paramMapToJsonObject(params, data);

    // serialize to Mendix Object
    JsonDeserializer.readJsonDataIntoMendixObject(rsr.getContext(), data, argObject, false);

    return argObject;
  }
  private void parseMultipartData(RestServiceRequest rsr, IMendixObject argO, JSONObject data)
      throws IOException, FileUploadException {
    boolean hasFile = false;

    for (FileItemIterator iter = servletFileUpload.getItemIterator(rsr.request); iter.hasNext(); ) {
      FileItemStream item = iter.next();
      if (!item.isFormField()) { // This is the file(?!)
        if (!isFileSource) {
          RestServices.LOGPUBLISH.warn(
              "Received request with binary data but input argument isn't a filedocument. Skipping. At: "
                  + rsr.request.getRequestURL().toString());
          continue;
        }
        if (hasFile)
          RestServices.LOGPUBLISH.warn(
              "Received request with multiple files. Only one is supported. At: "
                  + rsr.request.getRequestURL().toString());
        hasFile = true;
        Core.storeFileDocumentContent(
            rsr.getContext(), argO, determineFileName(item), item.openStream());
      } else data.put(item.getFieldName(), IOUtils.toString(item.openStream()));
    }
  }
  private void startEndpoint(String method, String path, String description) {
    String url = RestServices.getAbsoluteUrl(def.getName()) + path;
    if (isHTML) {
      String link =
          "<small>"
              + RestServices.getBaseUrl()
              + "</small>"
              + StringUtils.HTMLEncode(url.substring(RestServices.getBaseUrl().length()));
      if ("GET".equals(method)) link = "<a href='" + url + "'>" + link + "</a>";

      rsr.write("<h2>" + method + "&raquo;&nbsp;&nbsp;&nbsp;" + link + "</h2>")
          .write("<p>" + description + "</p>")
          .write("<table><tr><th>Parameter</th><th>Description</th></tr>");
    } else
      rsr.datawriter
          .object()
          .key("path")
          .value(method + " " + url)
          .key("description")
          .value(description)
          .key("params")
          .array();
  }
  private void writeOutputData(RestServiceRequest rsr, Object result)
      throws IOException, Exception {
    if (result == null) {
      // write nothing
    } else if (this.isFileTarget) {
      if (!Utils.hasDataAccess(Core.getMetaObject(argType), rsr.getContext()))
        throw new IllegalStateException(
            "Cannot serialize filedocument of type '"
                + argType
                + "', the object is not accessiable for users with role "
                + rsr.getContext().getSession().getUserRolesNames()
                + ". Please check the access rules");

      String filename =
          ((IMendixObject) result)
              .getValue(rsr.getContext(), FileDocument.MemberNames.Name.toString());
      if (filename != null && !filename.isEmpty())
        rsr.response.setHeader(
            RestServices.HEADER_CONTENTDISPOSITION,
            "attachment;filename=\"" + Utils.urlEncode(filename) + "\"");
      InputStream stream = Core.getFileDocumentContent(rsr.getContext(), (IMendixObject) result);
      IOUtils.copy(stream, rsr.response.getOutputStream());
    } else if (this.isReturnTypePrimitive) {
      rsr.write(result == null ? "" : String.valueOf(result));
    } else if (result instanceof List<?>) {
      rsr.startDoc();

      rsr.datawriter.array();
      for (Object item : (List<?>) result)
        rsr.datawriter.value(
            JsonSerializer.writeMendixObjectToJson(rsr.getContext(), (IMendixObject) item, true));
      rsr.datawriter.endArray();
      rsr.endDoc();
    } else if (result instanceof IMendixObject) {
      rsr.startDoc();
      rsr.datawriter.value(
          JsonSerializer.writeMendixObjectToJson(rsr.getContext(), (IMendixObject) result, true));
      rsr.endDoc();
    } else
      throw new IllegalStateException(
          "Unexpected result from microflow " + microflowname + ": " + result.getClass().getName());
  }
  public void serveServiceDescription() {
    rsr.startDoc();
    if (isHTML) {
      rsr.write(
          "<h1>Service: " + def.getName() + "</h1><a href='/" + RestServices.PATH_REST + "'>");
    }

    rsr.datawriter
        .object()
        .key("name")
        .value(def.getName())
        .key("description")
        .value(def.getDescription())
        .key("baseurl")
        .value(RestServices.getAbsoluteUrl(def.getName()))
        .key("worldreadable")
        .value("*".equals(def.getAccessRole()))
        .key("requiresETags")
        .value(def.getUseStrictVersioning());

    if (isHTML) rsr.datawriter.endObject();
    else rsr.datawriter.key("endpoints").array();

    startEndpoint("GET", "?" + RestServices.PARAM_ABOUT, "This page");
    addContentType();
    endEndpoint();

    if (def.getEnableListing()) {
      startEndpoint(
          "GET",
          "?" + RestServices.PARAM_COUNT,
          "Returns the amount of objects available in this service");
      addContentType();
      endEndpoint();

      startEndpoint("GET", "", "List the URL of all objects published by this service");
      addEndpointParam(
          RestServices.PARAM_DATA,
          "'true' or 'false'. Whether to list the URLs (false) of each of the objects, or output the objects themselves (true). Defaults to 'false'");
      addEndpointParam(RestServices.PARAM_OFFSET, "positive number, optional argument");
      addEndpointParam(RestServices.PARAM_LIMIT, "positive number, optional argument");
      addContentType();
      endEndpoint();
    }
    if (def.getEnableGet()) {
      startEndpoint(
          "GET",
          "<" + def.getSourceKeyAttribute() + ">",
          "Returns the object specified by the URL, which is retrieved from the database by using the given key.");
      addEndpointParam(
          RestServices.HEADER_IFNONEMATCH + " (header)",
          "If the current version of the object matches the ETag provided by this optional header, status 304 NOT MODIFIED will be returned instead of returning the whole objects. This header can be used for caching / performance optimization");
      addContentType();

      JSONObject schema = JSONSchemaBuilder.build(Core.getReturnType(def.getOnPublishMicroflow()));
      addEndpointParam("(request body)", schema);

      endEndpoint();
    }
    if (def.getEnableChangeLog()) {
      startEndpoint(
          "GET",
          "changes/list",
          "Returns a list of incremental changes that allows the client to synchronize with recent changes on the server");
      addEndpointParam(RestServices.PARAM_SINCE, SINCEPARAM_HELPTEXT);
      addContentType();
      endEndpoint();

      startEndpoint(
          "GET",
          "changes/feed",
          "Returns a list of incremental changes that allows the client to synchronize with recent changes on the server. The feed, in contrast to list, keeps the connection open to be able to push any new change directly to the client, without the client needing to actively request for new changes. (a.k.a. push over longpolling HTTP)");
      addEndpointParam(RestServices.PARAM_SINCE, SINCEPARAM_HELPTEXT);
      addEndpointParam(
          RestServices.PARAM_TIMEOUT,
          "Maximum time the current feed connecion is kept open. Defaults to 50 seconds to avoid firewall issues. Once this timeout exceeds, the connection is closed and the client should automatically reconnect. Use zero to never expire. Use a negative number to indicate that the connection should expire whenever the timeout is exceed, *or* when a new change arrives. This is useful for clients that cannot read partial responses");
      addContentType();
      endEndpoint();
    }
    if (def.getEnableCreate()) {
      startEndpoint(
          "POST",
          "",
          "Stores an object as new entry in the collection served by this service. Returns the (generated) key of the new object");
      addBodyParam();
      addEtagParam();
      endEndpoint();

      startEndpoint(
          "PUT",
          "<" + def.getSourceKeyAttribute() + ">",
          "Stores an object as new entry in the collection served by this service, under te given key. This key shouldn't exist yet.");
      addBodyParam();
      addEtagParam();
      endEndpoint();
    }
    if (def.getEnableUpdate()) {
      startEndpoint(
          "PUT",
          "<" + def.getSourceKeyAttribute() + ">",
          "Updates the object with the given key. If the key does not exist yet, "
              + (def.getEnableCreate() ? "the object will be created" : " the request will fail"));
      addBodyParam();
      addEtagParam();
      endEndpoint();
    }
    if (def.getEnableDelete()) {
      startEndpoint(
          "DELETE",
          "<" + def.getSourceKeyAttribute() + ">",
          "Deletes the object identified by the key");
      addBodyParam();
      addEtagParam();
      endEndpoint();
    }

    if (!isHTML) rsr.datawriter.endArray().endObject();

    rsr.endDoc();
  }
 public ServiceDescriber(RestServiceRequest rsr, DataServiceDefinition def) {
   this.rsr = rsr;
   this.def = def;
   this.isHTML = rsr.getResponseContentType() == ResponseType.HTML;
 }
 private void endEndpoint() {
   if (isHTML) rsr.write("</table>");
   else rsr.datawriter.endArray().endObject();
 }