RestEndpointData.java

/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License
 * at:
 *
 *   http://opensource.org/licenses/ecl2.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package org.opencastproject.runtimeinfo.rest;

import static org.apache.commons.lang3.StringUtils.isBlank;

import org.opencastproject.util.JaxbXmlSchemaGenerator;
import org.opencastproject.util.doc.DocData;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestResponse;

import org.apache.commons.lang3.StringEscapeUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class RestEndpointData implements Comparable<RestEndpointData> {

  /**
   * The name of the endpoint, which should be unique. In the documentation page, the same type of endpoints are shown
   * in ascending order of name.
   */
  private String name;

  /**
   * The HTTP method used to invoke the endpoint.
   */
  private String httpMethod;

  /**
   * The path for this endpoint (e.g. /search OR /add/{id}).
   */
  private String path;

  /**
   * The description of this endpoint.
   */
  private String description;

  /**
   * The body parameter of this endpoint.
   */
  private RestParamData bodyParam;

  /**
   * The list of path parameters of this endpoint.
   */
  private List<RestParamData> pathParams;

  /**
   * The list of required query parameters of this endpoint.
   */
  private List<RestParamData> requiredParams;

  /**
   * The list of optional query parameters of this endpoint.
   */
  private List<RestParamData> optionalParams;

  /**
   * The list of notes (i.e. extra information) of this endpoint.
   */
  private List<String> notes;

  /**
   * The list of formats returned by this endpoint.
   */
  private List<RestFormatData> formats;

  /**
   * The list of HTTP responses returned by this endpoint.
   */
  private List<StatusData> statuses;

  /**
   * The form for testing this endpoint in the documentation page.
   */
  private RestFormData form;

  /** The XML schema for data returned by this endpoint. */
  private String returnTypeSchema = null;

  /**
   * Create a new basic endpoint, you should use the add methods to fill in the rest of the information about the
   * endpoint data
   *
   * @param returnType
   *          the endpoint's return type
   * @param name
   *          the endpoint's name (this should be unique in the same type of endpoints)
   * @param httpMethod
   *          the HTTP method used for this endpoint
   * @param path
   *          the path for this endpoint (e.g. /search OR /add/{id})
   * @param description
   *          [optional] the description of this endpoint
   * @throws IllegalArgumentException
   *           if name is null, name is not alphanumeric, method is null, path is null or path is not valid.
   */
  public RestEndpointData(Class<?> returnType, String name, String httpMethod, String path, String description)
          throws IllegalArgumentException {
    if (!DocData.isValidName(name)) {
      throw new IllegalArgumentException("Name must not be null and must be alphanumeric.");
    }
    if ((httpMethod == null) || (httpMethod.isEmpty())) {
      throw new IllegalArgumentException("Method must not be null and must not be empty.");
    }
    if (!RestDocData.isValidPath(path)) {
      throw new IllegalArgumentException(String.format("Path '%s' must not be null and must look something like "
              + "/a/b/{c}.", path));
    }
    this.returnTypeSchema = JaxbXmlSchemaGenerator.getXmlSchema(returnType);
    this.name = name;
    this.httpMethod = httpMethod.toUpperCase();
    this.path = path;
    this.description = description;
  }

  /**
   * Returns a string representation of this object.
   *
   * @return a string representation of this object
   */
  @Override
  public String toString() {
    return "ENDP:" + name + ":" + httpMethod + " " + path + " :body=" + bodyParam + " :req=" + requiredParams
            + " :opt=" + optionalParams + " :formats=" + formats + " :status=" + statuses + " :form=" + form;
  }

  /**
   * Adds a body parameter to this endpoint. Once added, the body parameter becomes a required parameter.
   *
   * @param restParam
   *          a RestParameter annotation object corresponding to the body parameter
   *
   * @return the new RestParamData object in case you want to set attributes
   */
  public RestParamData addBodyParam(RestParameter restParam) {
    RestParamData.Type type = RestParamData.Type.valueOf(restParam.type().name());
    RestParamData param = new RestParamData("BODY", type, restParam.defaultValue(),
            restParam.description(), null);
    param.setRequired(true);
    bodyParam = param;
    return param;
  }

  /**
   * Adds a path parameter for this endpoint, this would be a parameter which is passed as part of the path (e.g.
   * /my/path/{param}) and thus must use a name which is safe to be placed in a URL and does not contain a slash (/)
   *
   * @param param
   *          the path parameter to add
   * @throws IllegalStateException
   *           if the type of the path parameter is FILE or TEXT
   */
  public void addPathParam(RestParamData param) throws IllegalStateException {
    if (RestParamData.Type.FILE.name().equals(param.getType())
            || RestParamData.Type.TEXT.name().equals(param.getType())) {
      throw new IllegalStateException("Cannot add path param of type FILE or TEXT.");
    }
    param.setRequired(true);
    param.setPath(true);
    if (pathParams == null) {
      pathParams = new Vector<RestParamData>(3);
    }
    pathParams.add(param);
  }

  /**
   * Adds a required form parameter for this endpoint, this would be a parameter which is passed encoded as part of the
   * request body (commonly referred to as a post or form parameter). <br>
   * WARNING: This should generally be reserved for endpoints which are used for processing, it is better to use path
   * params unless the required parameter is not part of an identifier for the resource.
   *
   * @param param
   *          the required parameter to add
   */
  public void addRequiredParam(RestParamData param) throws IllegalStateException {
    param.setRequired(true);
    param.setPath(false);
    if (requiredParams == null) {
      requiredParams = new Vector<RestParamData>(3);
    }
    requiredParams.add(param);
  }

  /**
   * Adds an optional parameter for this endpoint, this would be a parameter which is passed in the query string (for
   * GET) or encoded as part of the body otherwise (often referred to as a post or form parameter).
   *
   * @param param
   *          the optional parameter to add
   */
  public void addOptionalParam(RestParamData param) {
    param.setRequired(false);
    param.setPath(false);
    if (optionalParams == null) {
      optionalParams = new Vector<RestParamData>(3);
    }
    optionalParams.add(param);
  }

  /**
   * Adds a format for the return data for this endpoint.
   *
   * @param format
   *          a RestFormatData object
   */
  public void addFormat(RestFormatData format) {
    if (formats == null) {
      formats = new Vector<RestFormatData>(2);
    }
    formats.add(format);
  }

  /**
   * Adds a response status for this endpoint.
   *
   * @param restResponse
   *          a RestResponse object containing the HTTP response code and description
   */
  public void addStatus(RestResponse restResponse) {
    if (statuses == null) {
      statuses = new Vector<StatusData>(3);
    }
    statuses.add(new StatusData(restResponse));
  }

  /**
   * Adds a note for this endpoint.
   *
   * @param note
   *          a string providing more information about this endpoint
   * @throws IllegalArgumentException
   *           if note is blank (e.g. null, empty string)
   */
  public void addNote(String note) throws IllegalArgumentException {
    if (isBlank(note)) {
      throw new IllegalArgumentException("Note must not be null or blank.");
    }
    if (notes == null) {
      notes = new Vector<String>(3);
    }
    notes.add(note);
  }

  /**
   * Sets the test form for this endpoint, if this is null then no test form is rendered for this endpoint.
   *
   * @param form
   *          the test form object (null to clear the form)
   */
  public void setTestForm(RestFormData form) {
    this.form = form;
  }

  /**
   * Returns whether this endpoint's HTTP method is GET
   *
   * @return true if this endpoint method is GET, otherwise false
   */
  public boolean isGetMethod() {
    return "GET".equals(httpMethod);
  }

  /**
   * Returns the URL-encoded query string for a GET endpoint.
   *
   * @return the calculated query string for a GET endpoint (e.g. ?blah=1), will be urlencoded for html display
   */
  public String getQueryString() {
    String qs = "";
    if (isGetMethod()) {
      if (optionalParams != null && !optionalParams.isEmpty()) {
        StringBuilder sb = new StringBuilder();
        sb.append("?");
        for (RestParamData p : optionalParams) {
          if (sb.length() > 2) {
            sb.append("&");
          }
          sb.append(p.getName());
          sb.append("=");
          if (p.getDefaultValue() != null) {
            sb.append(p.getDefaultValue());
          } else {
            sb.append("{");
            sb.append(p.getName());
            sb.append("}");
          }
        }
        qs = StringEscapeUtils.escapeHtml4(sb.toString());
      }
    }
    return qs;
  }

  /**
   * Gets the name of this endpoint.
   *
   * @return the name of this endpoint
   */
  public String getName() {
    return name;
  }

  /**
   * Gets the name of HTTP method used to invoke this endpoint.
   *
   * @return the name of HTTP method used to invoke this endpoint
   */
  public String getMethod() {
    return httpMethod;
  }

  /**
   * Gets the path for this endpoint.
   *
   * @return the path for this endpoint
   */
  public String getPath() {
    return path;
  }

  /**
   * Gets the description of this endpoint.
   *
   * @return the description of this endpoint
   */
  public String getDescription() {
    return description;
  }

  /**
   * Gets the body parameter of this endpoint.
   *
   * @return the body parameter of this endpoint
   */
  public RestParamData getBodyParam() {
    return bodyParam;
  }

  /**
   * Gets the list of path parameters of this endpoint.
   *
   * @return the list of path parameters of this endpoint
   */
  public List<RestParamData> getPathParams() {
    if (pathParams == null) {
      pathParams = new ArrayList<RestParamData>(0);
    }
    return pathParams;
  }

  /**
   * Gets the list of required parameters of this endpoint.
   *
   * @return the list of required parameters of this endpoint
   */
  public List<RestParamData> getRequiredParams() {
    if (requiredParams == null) {
      requiredParams = new ArrayList<RestParamData>(0);
    }
    return requiredParams;
  }

  /**
   * Gets the list of optional parameters of this endpoint.
   *
   * @return list of optional parameters of this endpoint
   */
  public List<RestParamData> getOptionalParams() {
    if (optionalParams == null) {
      optionalParams = new ArrayList<RestParamData>(0);
    }
    return optionalParams;
  }

  /**
   * Gets the list of formats returned by this endpoint.
   *
   * @return the list of formats returned by this endpoint
   */
  public List<RestFormatData> getFormats() {
    if (formats == null) {
      formats = new ArrayList<RestFormatData>(0);
    }
    return formats;
  }

  /**
   * Gets the list of HTTP responses returned by this endpoint.
   *
   * @return the list of HTTP responses returned by this endpoint
   */
  public List<StatusData> getStatuses() {
    if (statuses == null) {
      statuses = new ArrayList<StatusData>(0);
    }
    return statuses;
  }

  /**
   * Gets list of notes (i.e. extra information) of this endpoint.
   *
   * @return the list of notes (i.e. extra information) of this endpoint
   */
  public List<String> getNotes() {
    if (notes == null) {
      notes = new ArrayList<>(0);
    }
    return notes;
  }

  /**
   * Gets the form for testing this endpoint in the documentation page.
   *
   * @return the form for testing this endpoint in the documentation page
   */
  public RestFormData getForm() {
    return form;
  }

  /**
   * Compares two RestEndpointData by their names so that the list of endpoints can be sorted.
   *
   * @param otherEndpoint
   *          the other endpoint object to compare to
   *
   * @return a negative integer, zero, or a positive integer as the name of the supplied endpoint is greater than, equal
   *         to, or less than this endpoint, ignoring case considerations.
   */
  @Override
  public int compareTo(RestEndpointData otherEndpoint) {
    return name.compareToIgnoreCase(otherEndpoint.name);
  }

  /**
   * @return the XML schema for this endpoint's return type
   */
  public String getReturnTypeSchema() {
    return returnTypeSchema;
  }

  public String getEscapedReturnTypeSchema() {
    return StringEscapeUtils.escapeXml(returnTypeSchema);
  }

}