Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api-v1): Add support for PDF files (DSP-1267) #1797

Merged
merged 19 commits into from Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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