LtiServiceGuiEndpoint.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.lti;

import org.opencastproject.lti.service.api.LtiFileUpload;
import org.opencastproject.lti.service.api.LtiService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestParameter.Type;
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 org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

/**
 * The endpoint for the LTI gui
 */
@Path("/lti-service-gui")
@RestService(
    name = "ltirestserviceguiendpoint",
    title = "LTI Service",
    notes = {},
    abstractText = "Provides operations to LTI clients"
)
@Component(
    immediate = true,
    service = LtiServiceGuiEndpoint.class,
    property = {
        "service.description=LTI Service GUI",
        "opencast.service.type=org.opencastproject.lti.service.remote",
        "opencast.service.path=/lti-service-gui"
    }
)
@JaxrsResource
public class LtiServiceGuiEndpoint {
  /* OSGi service references */
  private LtiService service;

  /** OSGi DI */
  @Reference
  public void setService(LtiService service) {
    this.service = service;
  }

  @GET
  @Path("/new/metadata")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getNewEventMetadata() {
    return Response.ok(this.service.getNewEventMetadata(), MediaType.APPLICATION_JSON).build();
  }

  @GET
  @Path("/{eventId}/metadata")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getEventMetadata(@PathParam("eventId") final String eventId) {
    try {
      return Response.ok(this.service.getEventMetadata(eventId), MediaType.APPLICATION_JSON).build();
    } catch (NotFoundException e) {
      return Response.status(Status.NOT_FOUND).build();
    } catch (UnauthorizedException e) {
      return Response.status(Status.UNAUTHORIZED).build();
    }
  }

  @POST
  @Path("/{eventId}/metadata")
  @Produces(MediaType.APPLICATION_JSON)
  public Response setEventMetadata(
      @PathParam("eventId") final String eventId,
      @FormParam("metadataJson") final String metadataJson
  ) {
    try {
      this.service.setEventMetadataJson(eventId, metadataJson);
      return Response.ok().build();
    } catch (NotFoundException e) {
      return Response.status(Status.NOT_FOUND).build();
    } catch (UnauthorizedException e) {
      return Response.status(Status.UNAUTHORIZED).build();
    }
  }

  @POST
  @Path("/{eventId}/copy")
  @Produces(MediaType.APPLICATION_JSON)
  public Response copyEventToSeries(
      @PathParam("eventId") final String eventId,
      @QueryParam("target_series") final String targetSeries
  ) {
    this.service.copyEventToSeries(eventId, targetSeries);
    return Response.ok().build();
  }

  @GET
  @Path("/jobs")
  @Produces(MediaType.APPLICATION_JSON)
  public Response listJobs(@QueryParam("seriesId") String seriesId) {
    final List<Map<String, String>> results = service.listJobs(seriesId).stream().map(e -> {
      Map<String, String> eventMap = new HashMap<>();
      eventMap.put("title", e.getTitle());
      eventMap.put("status", e.getStatus());
      return eventMap;
    }).collect(Collectors.toList());
    return Response.status(Status.OK).entity(new Gson().toJson(results, List.class)).build();
  }

  @POST
  @Path("/")
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @RestQuery(
      name = "uploadevent",
      description = "Creates an event in a multipart request.",
      returnDescription = "",
      restParameters = {
          @RestParameter(
              name = "presenter",
              description = "Presenter movie track",
              isRequired = false,
              type = Type.FILE
          ),
          @RestParameter(
              name = "license",
              description = "License chosen",
              isRequired = false,
              type = Type.STRING
          ),
          @RestParameter(
              name = "seriesName",
              description = "Series name",
              isRequired = false,
              type = Type.STRING
          ),
          @RestParameter(
              name = "isPartOf",
              description = "Series ID",
              isRequired = false,
              type = Type.STRING
          ),
          @RestParameter(
              name = "processing",
              description = "Processing instructions task configuration",
              isRequired = false,
              type = Type.STRING
          )
      },
      responses = {
          @RestResponse(
              description = "A new event is created and its identifier is returned in the Location header.",
              responseCode = HttpServletResponse.SC_CREATED
          ),
          @RestResponse(
              description = "The event could not be created due to a scheduling conflict.",
              responseCode = HttpServletResponse.SC_CONFLICT
          ),
          @RestResponse(
              description = "The request is invalid or inconsistent..",
              responseCode = HttpServletResponse.SC_BAD_REQUEST
          )
      }
  )
  public Response createNewEvent(@HeaderParam("Accept") String acceptHeader, @Context HttpServletRequest request) {
    String seriesId = "";
    try {
      String captions = null;
      String captionFormat = null;
      String captionLanguage = null;
      String metadata = null;
      String eventId = null;
      for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
        final FileItemStream item = iter.next();
        final String fieldName = item.getFieldName();
        if ("eventId".equals(fieldName)) {
          eventId = Streams.asString(item.openStream());
        } else if ("metadata".equals(fieldName)) {
          metadata = Streams.asString(item.openStream());
        } else if ("captions".equals(fieldName)) {
          captions = Streams.asString(item.openStream());
        } else if ("captionFormat".equals(fieldName)) {
          captionFormat = Streams.asString(item.openStream());
        } else if ("captionLanguage".equals(fieldName)) {
          captionLanguage = Streams.asString(item.openStream());
        } else if ("seriesId".equals(fieldName)) {
          final String fieldValue = Streams.asString(item.openStream());
          if (!fieldValue.isEmpty()) {
            seriesId = fieldValue;
          }
        } else {
          service.upsertEvent(
                  new LtiFileUpload(item.openStream(), item.getName()),
                  captions,
                  captionFormat,
                  captionLanguage,
                  eventId,
                  seriesId,
                  metadata);
          return Response.ok().build();
        }
      }
      service.upsertEvent(
              null,
              captions,
              captionFormat,
              captionLanguage,
              eventId,
              seriesId,
              metadata);
      return Response.ok().build();
    } catch (FileUploadException | IOException e) {
      return Response.status(Status.INTERNAL_SERVER_ERROR).entity("error while uploading").build();
    } catch (NotFoundException e) {
      return Response.status(Status.NOT_FOUND).build();
    } catch (UnauthorizedException e) {
      return Response.status(Status.UNAUTHORIZED).build();
    }
  }

  @DELETE
  @Path("{eventId}")
  @RestQuery(
      name = "deleteevent",
      description = "Deletes an event.",
      returnDescription = "",
      pathParameters = {
          @RestParameter(name = "eventId", description = "The event id", isRequired = true, type = Type.STRING)
      },
      responses = {
          @RestResponse(
              description = "The event has been deleted.",
              responseCode = HttpServletResponse.SC_NO_CONTENT
          ),
          @RestResponse(
              description = "The specified event does not exist.",
              responseCode = HttpServletResponse.SC_NOT_FOUND
          )
      }
  )
  public Response deleteEvent(@HeaderParam("Accept") String acceptHeader, @PathParam("eventId") String id) {
    service.delete(id);
    return Response.noContent().build();
  }
}