All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.digitalcollections.iiif.hymir.presentation.frontend.IIIFPresentationApiController Maven / Gradle / Ivy

package de.digitalcollections.iiif.hymir.presentation.frontend;

import de.digitalcollections.commons.springboot.metrics.MetricsService;
import de.digitalcollections.iiif.hymir.config.CustomResponseHeaders;
import de.digitalcollections.iiif.hymir.model.exception.InvalidDataException;
import de.digitalcollections.iiif.hymir.model.exception.ResolvingException;
import de.digitalcollections.iiif.hymir.presentation.business.api.PresentationService;
import de.digitalcollections.iiif.hymir.util.UrlRules;
import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList;
import de.digitalcollections.iiif.model.sharedcanvas.Canvas;
import de.digitalcollections.iiif.model.sharedcanvas.Collection;
import de.digitalcollections.iiif.model.sharedcanvas.Manifest;
import de.digitalcollections.iiif.model.sharedcanvas.Range;
import de.digitalcollections.iiif.model.sharedcanvas.Sequence;
import de.digitalcollections.model.api.identifiable.resource.exceptions.ResourceNotFoundException;
import java.net.URI;
import java.time.Instant;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;

@Controller
@RequestMapping("${custom.iiif.presentation.urlPrefix:/presentation/v2}")
public class IIIFPresentationApiController {

  private static final Logger LOGGER = LoggerFactory.getLogger(IIIFPresentationApiController.class);
  public static final String VERSION = "v2";

  @Autowired protected CustomResponseHeaders customResponseHeaders;

  @Autowired private PresentationService presentationService;

  @Autowired private MetricsService metricsService;

  // We set the header ourselves, since using @CrossOrigin doesn't expose "*", but always sets the
  // requesting domain
  // @CrossOrigin(allowedHeaders = {"*"}, origins = {"*"})
  @RequestMapping(
      value = {"{identifier}/manifest", "{identifier}"},
      method = RequestMethod.GET,
      produces = "application/json")
  @ResponseBody
  public Manifest getManifest(
      @PathVariable String identifier, WebRequest request, HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.isInsecure(identifier)) {
      resp.setStatus(400);
      return null;
    }
    // Return 304 if the manifest has seen no modifications since the requested time
    long modified = presentationService.getManifestModificationDate(identifier).toEpochMilli();
    if (request.checkNotModified(modified)) {
      return null;
    }
    long generationDuration = System.currentTimeMillis();
    Manifest manifest = presentationService.getManifest(identifier);
    generationDuration = System.currentTimeMillis() - generationDuration;
    metricsService.increaseCounterWithDurationAndPercentiles(
        "generations", "manifest", generationDuration);
    resp.setDateHeader("Last-Modified", modified);
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationManifest()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    LOGGER.info("Serving manifest for {}", identifier);
    return manifest;
  }

  @RequestMapping(
      value = {"{identifier}/manifest", "{identifier}"},
      method = RequestMethod.HEAD)
  public void checkManifest(@PathVariable String identifier, HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException {
    if (UrlRules.isInsecure(identifier)) {
      resp.setStatus(400);
      return;
    }
    Instant modDate = presentationService.getManifestModificationDate(identifier);
    resp.setDateHeader("Last-Modified", modDate.toEpochMilli());
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationManifest()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    resp.setStatus(HttpStatus.SC_OK);
  }

  @RequestMapping(
      value = {"{manifestId}/canvas/{canvasId}"},
      method = RequestMethod.GET)
  @ResponseBody
  public Canvas getCanvas(
      @PathVariable String manifestId,
      @PathVariable String canvasId,
      HttpServletRequest req,
      HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.anyIsInsecure(manifestId, canvasId)) {
      resp.setStatus(400);
      return null;
    }
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationManifest()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    return presentationService.getCanvas(manifestId, getOriginalUri(req));
  }

  @RequestMapping(
      value = {"{manifestId}/range/{rangeId}"},
      method = RequestMethod.GET)
  @ResponseBody
  public Range getRange(
      @PathVariable String manifestId,
      @PathVariable String rangeId,
      HttpServletRequest req,
      HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.anyIsInsecure(manifestId, rangeId)) {
      resp.setStatus(400);
      return null;
    }
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationManifest()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    return presentationService.getRange(manifestId, getOriginalUri(req));
  }

  @RequestMapping(
      value = {"{manifestId}/sequence/{sequenceId}"},
      method = RequestMethod.GET)
  @ResponseBody
  public Sequence getSequence(
      @PathVariable String manifestId,
      @PathVariable String sequenceId,
      HttpServletRequest req,
      HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.anyIsInsecure(manifestId, sequenceId)) {
      resp.setStatus(400);
      return null;
    }
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationManifest()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    return presentationService.getSequence(manifestId, getOriginalUri(req));
  }

  @RequestMapping(
      value = {"collection/{identifier}"},
      method = {RequestMethod.GET, RequestMethod.HEAD},
      produces = "application/json")
  @ResponseBody
  public Collection getCollection(
      @PathVariable String identifier, WebRequest request, HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.isInsecure(identifier)) {
      resp.setStatus(400);
      return null;
    }
    long modified = presentationService.getCollectionModificationDate(identifier).toEpochMilli();
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationCollection()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });
    if (request.checkNotModified(modified)) {
      return null;
    }
    Collection collection = presentationService.getCollection(identifier);
    LOGGER.info("Serving collection for {}", identifier);
    return collection;
  }

  @GetMapping(
      value = {"{identifier}/list/{name}/{canvasId}"},
      produces = "application/json")
  @ResponseBody
  public AnnotationList getAnnotationList(
      @PathVariable String identifier,
      @PathVariable String name,
      @PathVariable String canvasId,
      HttpServletResponse resp)
      throws ResolvingException, ResourceNotFoundException, InvalidDataException {
    if (UrlRules.anyIsInsecure(identifier, name, canvasId)) {
      resp.setStatus(400);
      return null;
    }
    resp.addHeader("Access-Control-Allow-Origin", "*");

    customResponseHeaders
        .forPresentationAnnotationList()
        .forEach(
            customResponseHeader -> {
              resp.setHeader(customResponseHeader.getName(), customResponseHeader.getValue());
            });

    LOGGER.info("Serving annotation list for {}-{}_{}", name, identifier, canvasId);
    return presentationService.getAnnotationList(identifier, name, canvasId);
  }

  /**
   * Return the URL as it was originally received from a possible frontend proxy.
   *
   * @param request Incoming request
   * @return URL as it was received at the frontend proxy
   */
  private URI getOriginalUri(HttpServletRequest request) {
    String requestUrl = request.getRequestURL().toString();
    String incomingScheme = URI.create(requestUrl).getScheme();
    String originalScheme = request.getHeader("X-Forwarded-Proto");
    if (originalScheme != null && !incomingScheme.equals(originalScheme)) {
      return URI.create(requestUrl.replaceFirst("^" + incomingScheme, originalScheme));
    } else {
      return URI.create(requestUrl);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy