StudioEndpoint.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.studio.endpoint;

import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;

import org.opencastproject.elasticsearch.api.SearchIndexException;
import org.opencastproject.elasticsearch.api.SearchResultItem;
import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
import org.opencastproject.security.api.Permissions;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.studio.endpoint.dto.SeriesDto;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/studio-api")
@RestService(
    name = "studioservice",
    title = "Studio REST Endpoint",
    abstractText = "The Internal studio API is not stable!",
    notes = {
        "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
        "If the service is down or not working it will return a status 503, this means the the "
            + "underlying service is not working and is either restarting or has failed",
        "A status code 500 means a general failure has occurred which is not recoverable and was "
            + "not anticipated. In other words, there is a bug! You should file an error report "
            + "with your server logs from the time when the error occurred: "
            + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
    }
)
@Component(
    immediate = true,
    service = StudioEndpoint.class,
    property = {
        "service.description=Studio REST Endpoint",
        "opencast.service.type=org.opencastproject.studio",
        "opencast.service.path=/studio-api"
    }
)
@JaxrsResource
public class StudioEndpoint {

  private static final Logger logger = LoggerFactory.getLogger(StudioEndpoint.class);

  private final Gson gson;

  private ElasticsearchIndex elasticsearchIndex;
  private SecurityService securityService;

  {
    gson = new GsonBuilder().serializeNulls().create();
  }

  @GET
  @Path("/series.json")
  @Produces(MediaType.APPLICATION_JSON)
  @RestQuery(
      name = "getSeries",
      description = "Returns all series for which the current user has write permissions.",
      returnDescription = "A list of JSON series objects",
      restParameters = {
          @RestParameter(
              name = "filter",
              isRequired = false,
              description = "Usage <Filter Name>:<Value to Filter With>. Filters can combine using a comma \",\"."
                  + " Available Filters: title, textFilter.",
              type = STRING
          ),
      },
      responses = {
          @RestResponse(
              description = "Returns a list of series.",
              responseCode = HttpServletResponse.SC_OK
          )
      }
  )
  public Response getSeries(@QueryParam("filter") String filter) {
    SeriesSearchQuery query = new SeriesSearchQuery(securityService.getOrganization().getId(),
        securityService.getUser());
    query.withoutActions();
    query.withAction(Permissions.Action.WRITE);

    if (filter != null && !filter.isBlank()) {
      for (String f : filter.split(",")) {
        String[] filterTuple = f.split(":", 2);
        if (filterTuple.length < 2) {
          throw new WebApplicationException(Response
              .status(Response.Status.BAD_REQUEST)
              .entity(String.format("Filter %s is not valid: %s", filterTuple[0], filter))
              .build());
        }
        String name = filterTuple[0];
        String value = filterTuple[1];

        if ("title".equals(name)) {
          query.withTitle(value);
        } else if ("textFilter".equals(name)) {
          query.withText("*" + value + "*");
        } else {
          throw new WebApplicationException(Response
              .status(Response.Status.BAD_REQUEST)
              .entity(String.format("Unknown filter criteria %s", name))
              .build());
        }
      }
    }

    try {
      var items = Arrays.stream(elasticsearchIndex.getByQuery(query).getItems())
              .map(SearchResultItem::getSource)
              .map(SeriesDto::create)
              .collect(Collectors.toList());
      return Response.ok(gson.toJson(items)).build();
    } catch (SearchIndexException e) {
      throw new WebApplicationException(e);
    }

  }

  @Reference
  public void setElasticsearchIndex(ElasticsearchIndex elasticsearchIndex) {
    this.elasticsearchIndex = elasticsearchIndex;
  }

  @Reference
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }
}