Skip to content

Commit

Permalink
feat(api-v1): Add support for PDF files (DSP-1267) (#1797)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Geer committed Feb 3, 2021
1 parent 3251a74 commit c3b2e84
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 16 deletions.
13 changes: 12 additions & 1 deletion test_data/all_data/permissions-data.ttl
Expand Up @@ -51,7 +51,18 @@

knora-admin:forProperty knora-base:hasStillImageFileValue ;

knora-base:hasPermissions "M knora-admin:Creator,knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"^^xsd:string .
knora-base:hasPermissions "M knora-admin:Creator,knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser"^^xsd:string .

### Default Object Access Permissions on knora-base:hasDocumentFileValue property
<http://rdfh.ch/permissions/0000/001-d4>

rdf:type knora-admin:DefaultObjectAccessPermission ;

knora-admin:forProject knora-admin:SystemProject ;

knora-admin:forProperty knora-base:hasDocumentFileValue ;

knora-base:hasPermissions "M knora-admin:Creator,knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser"^^xsd:string .



Expand Down
Expand Up @@ -88,6 +88,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
makeStillImageValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.TextFileValue =>
makeTextFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.DocumentFileValue =>
makeDocumentFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.LinkValue => makeLinkValue(valueProps, responderManager, userProfile)
}
}
Expand All @@ -106,6 +108,16 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
s"${settings.externalSipiIIIFGetUrl}/${imageFileValueV1.projectShortcode}/${imageFileValueV1.internalFilename}/full/${imageFileValueV1.dimX},${imageFileValueV1.dimY}/0/default.jpg"
}

/**
* Creates a URL for accessing a document file via Sipi.
*
* @param documentFileValueV1 the document file value.
* @return a Sipi URL.
*/
def makeSipiDocumentGetUrlFromFilename(documentFileValueV1: DocumentFileValueV1): String = {
s"${settings.externalSipiIIIFGetUrl}/${documentFileValueV1.projectShortcode}/${documentFileValueV1.internalFilename}/file"
}

/**
* Creates a URL for accessing a text file via Sipi.
*
Expand Down Expand Up @@ -168,6 +180,16 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
ny = Some(stillImageFileValueV1.dimY),
path = makeSipiImageGetUrlFromFilename(stillImageFileValueV1)
)

case documentFileValueV1: DocumentFileValueV1 =>
LocationV1(
format_name = mimeType2V1Format(documentFileValueV1.internalMimeType),
origname = documentFileValueV1.originalFilename,
nx = documentFileValueV1.dimX,
ny = documentFileValueV1.dimY,
path = makeSipiDocumentGetUrlFromFilename(documentFileValueV1)
)

case textFileValue: TextFileValueV1 =>
LocationV1(
format_name = mimeType2V1Format(textFileValue.internalMimeType),
Expand Down Expand Up @@ -343,6 +365,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {

case _: TextFileValueV1 => basicObjectResponse

case _: DocumentFileValueV1 => basicObjectResponse

case _: HierarchicalListValueV1 => basicObjectResponse

case _: ColorValueV1 => basicObjectResponse
Expand Down Expand Up @@ -804,6 +828,31 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
))
}

/**
* Converts a [[ValueProps]] into a [[DocumentFileValueV1]].
*
* @param valueProps a [[ValueProps]] representing the SPARQL query results to be converted.
* @return a [[DocumentFileValueV1]].
*/
private def makeDocumentFileValue(
valueProps: ValueProps,
projectShortcode: String,
responderManager: ActorRef,
userProfile: UserADM)(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ApiValueV1] = {
val predicates = valueProps.literalData

Future(
DocumentFileValueV1(
internalMimeType = predicates(OntologyConstants.KnoraBase.InternalMimeType).literals.head,
internalFilename = predicates(OntologyConstants.KnoraBase.InternalFilename).literals.head,
originalFilename = predicates.get(OntologyConstants.KnoraBase.OriginalFilename).map(_.literals.head),
projectShortcode = projectShortcode,
pageCount = predicates(OntologyConstants.KnoraBase.PageCount).literals.head.toInt,
dimX = predicates.get(OntologyConstants.KnoraBase.DimX).flatMap(_.literals.headOption.map(_.toInt)),
dimY = predicates.get(OntologyConstants.KnoraBase.DimY).flatMap(_.literals.headOption.map(_.toInt))
))
}

/**
* Converts a [[ValueProps]] into a [[LinkValueV1]].
*
Expand Down
Expand Up @@ -35,12 +35,7 @@ import org.knora.webapi.messages.v1.responder.resourcemessages.LocationV1
import org.knora.webapi.messages.v1.responder.{KnoraRequestV1, KnoraResponseV1}
import org.knora.webapi.messages.v2.responder.UpdateResultInProject
import org.knora.webapi.messages.v2.responder.standoffmessages._
import org.knora.webapi.messages.v2.responder.valuemessages.{
FileValueContentV2,
FileValueV2,
StillImageFileValueContentV2,
TextFileValueContentV2
}
import org.knora.webapi.messages.v2.responder.valuemessages._
import org.knora.webapi.messages.{OntologyConstants, StringFormatter}
import spray.json._

Expand Down Expand Up @@ -1574,6 +1569,75 @@ case class StillImageFileValueV1(internalMimeType: String,
}
}

/**
* A representation of a document in a binary format.
*
* @param internalMimeType the MIME-type of the internal representation.
* @param internalFilename the internal filename of the object.
* @param originalFilename the original filename of the object at the time of the import.
* @param pageCount the number of pages in the document.
* @param dimX the X dimension of the object.
* @param dimY the Y dimension of the object.
*/
case class DocumentFileValueV1(internalMimeType: String,
internalFilename: String,
originalFilename: Option[String] = None,
originalMimeType: Option[String] = None,
projectShortcode: String,
pageCount: Int,
dimX: Option[Int],
dimY: Option[Int])
extends FileValueV1 {
def valueTypeIri: IRI = OntologyConstants.KnoraBase.DocumentFileValue

def toJsValue: JsValue = ApiValueV1JsonProtocol.documentFileValueV1Format.write(this)

override def toString: String = internalFilename

/**
* Checks if a new document file value would duplicate an existing document file value.
*
* @param other another [[ValueV1]].
* @return `true` if `other` is a duplicate of `this`.
*/
override def isDuplicateOfOtherValue(other: ApiValueV1): Boolean = {
other match {
case documentFileValueV1: DocumentFileValueV1 => documentFileValueV1 == this
case otherValue =>
throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${otherValue.valueTypeIri}")
}
}

/**
* Checks if a new version of a document file value would be redundant given the current version of the value.
*
* @param currentVersion the current version of the value.
* @return `true` if this [[UpdateValueV1]] is redundant given `currentVersion`.
*/
override def isRedundant(currentVersion: ApiValueV1): Boolean = {
currentVersion match {
case documentFileValueV1: DocumentFileValueV1 => documentFileValueV1 == this
case other =>
throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${other.valueTypeIri}")
}
}

override def toFileValueContentV2: FileValueContentV2 = {
DocumentFileValueContentV2(
ontologySchema = InternalSchema,
fileValue = FileValueV2(
internalFilename = internalFilename,
internalMimeType = internalMimeType,
originalFilename = originalFilename,
originalMimeType = Some(internalMimeType)
),
pageCount = pageCount,
dimX = dimX,
dimY = dimY
)
}
}

case class MovingImageFileValueV1(internalMimeType: String,
internalFilename: String,
originalFilename: Option[String],
Expand Down Expand Up @@ -1756,6 +1820,7 @@ object ApiValueV1JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol
implicit val valueGetResponseV1Format: RootJsonFormat[ValueGetResponseV1] = jsonFormat7(ValueGetResponseV1)
implicit val dateValueV1Format: JsonFormat[DateValueV1] = jsonFormat5(DateValueV1)
implicit val stillImageFileValueV1Format: JsonFormat[StillImageFileValueV1] = jsonFormat7(StillImageFileValueV1)
implicit val documentFileValueV1Format: JsonFormat[DocumentFileValueV1] = jsonFormat8(DocumentFileValueV1)
implicit val textFileValueV1Format: JsonFormat[TextFileValueV1] = jsonFormat5(TextFileValueV1)
implicit val movingImageFileValueV1Format: JsonFormat[MovingImageFileValueV1] = jsonFormat5(MovingImageFileValueV1)
implicit val valueVersionV1Format: JsonFormat[ValueVersionV1] = jsonFormat3(ValueVersionV1)
Expand Down
Expand Up @@ -2764,8 +2764,8 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo
valueUtilV1.fileValueV12LocationV1(fullSizeImageFileValueToPreview(fullSizeImageFileValue))
}

// Convert the full-resolution file values into LocationV1 objects as required by Knora API v1.
locations: Seq[LocationV1] = preview.toVector ++ fullSizeImageFileValues.flatMap { fileValueV1 =>
// Convert the file values into LocationV1 objects as required by Knora API v1.
locations: Seq[LocationV1] = preview.toVector ++ fileValues.flatMap { fileValueV1 =>
createMultipleImageResolutions(fileValueV1).map(oneResolution =>
valueUtilV1.fileValueV12LocationV1(oneResolution))
}
Expand Down
26 changes: 25 additions & 1 deletion webapi/src/main/scala/org/knora/webapi/routing/RouteUtilV1.scala
Expand Up @@ -33,7 +33,12 @@ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.store.sipimessages.GetFileMetadataResponse
import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2
import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2.TextWithStandoffTagsV2
import org.knora.webapi.messages.v1.responder.valuemessages.{FileValueV1, StillImageFileValueV1, TextFileValueV1}
import org.knora.webapi.messages.v1.responder.valuemessages.{
DocumentFileValueV1,
FileValueV1,
StillImageFileValueV1,
TextFileValueV1
}
import org.knora.webapi.messages.v1.responder.{KnoraRequestV1, KnoraResponseV1}
import org.knora.webapi.messages.v2.responder.standoffmessages.{GetMappingRequestV2, GetMappingResponseV2}
import org.knora.webapi.settings.KnoraSettingsImpl
Expand Down Expand Up @@ -257,6 +262,13 @@ object RouteUtilV1 {
"text/plain"
)

/**
* MIME types used in Sipi to store document files.
*/
private val documentMimeTypes: Set[String] = Set(
"application/pdf"
)

/**
* Converts file metadata from Sipi into a [[FileValueV1]].
*
Expand Down Expand Up @@ -287,6 +299,18 @@ object RouteUtilV1 {
originalMimeType = fileMetadataResponse.originalMimeType,
projectShortcode = projectShortcode
)
} else if (documentMimeTypes.contains(fileMetadataResponse.internalMimeType)) {
DocumentFileValueV1(
internalFilename = filename,
internalMimeType = fileMetadataResponse.internalMimeType,
originalFilename = fileMetadataResponse.originalFilename,
originalMimeType = fileMetadataResponse.originalMimeType,
projectShortcode = projectShortcode,
pageCount = fileMetadataResponse.pageCount.getOrElse(
throw SipiException(s"Sipi did not return the page count of the document")),
dimX = fileMetadataResponse.width,
dimY = fileMetadataResponse.height
)
} else {
throw BadRequestException(s"MIME type ${fileMetadataResponse.internalMimeType} not supported in Knora API v1")
}
Expand Down
Expand Up @@ -69,6 +69,12 @@ class SipiConnector extends Actor with ActorLogging {

private val httpClient: CloseableHttpClient = HttpClients.custom.setDefaultRequestConfig(sipiRequestConfig).build

// Sipi's /knora.json route only returns the correct original filename for images.
private val internalMimeTypesForWhichSipiReturnsTheCorrectOriginalFilename: Set[String] = Set(
"image/jpx",
"image/jp2"
)

override def receive: Receive = {
case getFileMetadataRequest: GetFileMetadataRequest =>
try2Message(sender(), getFileMetadata(getFileMetadataRequest), log)
Expand Down Expand Up @@ -128,7 +134,10 @@ class SipiConnector extends Actor with ActorLogging {
sipiResponse: SipiKnoraJsonResponse = sipiResponseStr.parseJson.convertTo[SipiKnoraJsonResponse]
} yield
GetFileMetadataResponse(
originalFilename = sipiResponse.originalFilename,
originalFilename =
if (internalMimeTypesForWhichSipiReturnsTheCorrectOriginalFilename.contains(sipiResponse.internalMimeType))
sipiResponse.originalFilename
else None,
originalMimeType = sipiResponse.originalMimeType,
internalMimeType = sipiResponse.internalMimeType,
width = sipiResponse.width,
Expand Down
Expand Up @@ -265,6 +265,44 @@ DELETE {
}
}

case documentFileValue: DocumentFileValueV1 => {
?newValue knora-base:internalFilename """@documentFileValue.internalFilename""" .
?newValue knora-base:internalMimeType """@documentFileValue.internalMimeType""" .
?newValue knora-base:pageCount @documentFileValue.pageCount .

@documentFileValue.dimX match {
case Some(definedDimX) => {
?newValue knora-base:dimX @definedDimX .
}

case None => {}
}

@documentFileValue.dimY match {
case Some(definedDimY) => {
?newValue knora-base:dimY @definedDimY .
}

case None => {}
}

@documentFileValue.originalFilename match {
case Some(definedOriginalFilename) => {
?newValue knora-base:originalFilename """@definedOriginalFilename""" .
}

case None => {}
}

@documentFileValue.originalMimeType match {
case Some(definedOriginalMimeType) => {
?newValue knora-base:originalMimeType """@definedOriginalMimeType""" .
}

case None => {}
}
}

case listValue: HierarchicalListValueV1 => {

?newValue knora-base:valueHasListNode <@listValue.hierarchicalListIri> .
Expand Down
Expand Up @@ -221,6 +221,44 @@
}
}

case documentFileValue: DocumentFileValueV1 => {
<@newValueIri> knora-base:internalFilename """@documentFileValue.internalFilename""" ;
knora-base:internalMimeType """@documentFileValue.internalMimeType""" ;
knora-base:pageCount @documentFileValue.pageCount .

@documentFileValue.dimX match {
case Some(definedDimX) => {
<@newValueIri> knora-base:dimX @definedDimX .
}

case None => {}
}

@documentFileValue.dimY match {
case Some(definedDimY) => {
<@newValueIri> knora-base:dimY @definedDimY .
}

case None => {}
}

@documentFileValue.originalFilename match {
case Some(definedOriginalFilename) => {
<@newValueIri> knora-base:originalFilename """@definedOriginalFilename""" .
}

case None => {}
}

@documentFileValue.originalMimeType match {
case Some(definedOriginalMimeType) => {
<@newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" .
}

case None => {}
}
}

case listValue: HierarchicalListValueV1 => {

<@newValueIri> knora-base:valueHasListNode <@listValue.hierarchicalListIri> .
Expand Down

0 comments on commit c3b2e84

Please sign in to comment.