From bbb42f6fa5351dd5ec76eb79dc251b6f4f4b3d8c Mon Sep 17 00:00:00 2001 From: Benjamin Geer Date: Fri, 7 Feb 2020 16:53:27 +0100 Subject: [PATCH] feat(sipi): Improve error message if XSL file not found (#1590) --- webapi/_test_data/all_data/anything-data.ttl | 22 +++ .../e2e/v1/KnoraSipiIntegrationV1ITSpec.scala | 151 ++++++++++-------- .../webapi/messages/RequestWithSender.scala | 27 ++++ .../store/sipimessages/SipiMessages.scala | 4 +- .../responders/v2/ResourcesResponderV2.scala | 6 +- .../responders/v2/StandoffResponderV2.scala | 134 ++++++++-------- .../org/knora/webapi/store/StoreManager.scala | 8 +- .../webapi/store/iiif/SipiConnector.scala | 117 +++++++------- 8 files changed, 274 insertions(+), 195 deletions(-) create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/RequestWithSender.scala diff --git a/webapi/_test_data/all_data/anything-data.ttl b/webapi/_test_data/all_data/anything-data.ttl index 7b1cdf479b..881a152960 100644 --- a/webapi/_test_data/all_data/anything-data.ttl +++ b/webapi/_test_data/all_data/anything-data.ttl @@ -1695,3 +1695,25 @@ knora-base:hasRootNode ; knora-base:listNodePosition 1; rdfs:label "Tree list node 11"@en . + + a knora-base:XSLTransformation; + knora-base:attachedToProject ; + knora-base:attachedToUser ; + knora-base:creationDate "2020-02-06T11:55:47.860023Z"^^xsd:dateTime; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; + knora-base:hasTextFileValue ; + knora-base:isDeleted false; + rdfs:label "BEOL header XSLT" . + + a knora-base:TextFileValue; + knora-base:attachedToUser ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; + knora-base:internalFilename "missing.xsl"; + knora-base:internalMimeType "text/xml"; + knora-base:isDeleted false; + knora-base:originalFilename "header.xsl"; + knora-base:originalMimeType "text/xml"; + knora-base:valueCreationDate "2020-02-06T11:55:47.860023Z"^^xsd:dateTime; + knora-base:valueHasOrder 0; + knora-base:valueHasString "header.xsl"; + knora-base:valueHasUUID "geQNPU4USB61YhNHHarQ5A" . diff --git a/webapi/src/it/scala/org/knora/webapi/e2e/v1/KnoraSipiIntegrationV1ITSpec.scala b/webapi/src/it/scala/org/knora/webapi/e2e/v1/KnoraSipiIntegrationV1ITSpec.scala index df8ec12895..4c1a7726f5 100644 --- a/webapi/src/it/scala/org/knora/webapi/e2e/v1/KnoraSipiIntegrationV1ITSpec.scala +++ b/webapi/src/it/scala/org/knora/webapi/e2e/v1/KnoraSipiIntegrationV1ITSpec.scala @@ -47,9 +47,9 @@ object KnoraSipiIntegrationV1ITSpec { } /** - * End-to-End (E2E) test specification for testing Knora-Sipi integration. Sipi must be running with the config file - * `sipi.knora-docker-config.lua`. - */ + * End-to-End (E2E) test specification for testing Knora-Sipi integration. Sipi must be running with the config file + * `sipi.knora-docker-config.lua`. + */ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV1ITSpec.config) with TriplestoreJsonProtocol { override lazy val rdfDataObjects: List[RdfDataObject] = List( @@ -74,14 +74,15 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV private val pathToBEOLLetterMapping = "_test_data/test_route/texts/beol/testLetter/beolMapping.xml" private val pathToBEOLBulkXML = "_test_data/test_route/texts/beol/testLetter/bulk.xml" private val letterIri = new MutableTestIri + private val gravsearchTemplateIri = new MutableTestIri /** - * Adds the IRI of a XSL transformation to the given mapping. - * - * @param mapping the mapping to be updated. - * @param xsltIri the Iri of the XSLT to be added. - * @return the updated mapping. - */ + * Adds the IRI of a XSL transformation to the given mapping. + * + * @param mapping the mapping to be updated. + * @param xsltIri the Iri of the XSLT to be added. + * @return the updated mapping. + */ private def addXSLTIriToMapping(mapping: String, xsltIri: String): String = { val mappingXML: Elem = XML.loadString(mapping) @@ -113,12 +114,12 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV } /** - * Given the id originally provided by the client, gets the generated IRI from a bulk import response. - * - * @param bulkResponse the response from the bulk import route. - * @param clientID the client id to look for. - * @return the Knora IRI of the resource. - */ + * Given the id originally provided by the client, gets the generated IRI from a bulk import response. + * + * @param bulkResponse the response from the bulk import route. + * @param clientID the client id to look for. + * @return the Knora IRI of the resource. + */ private def getResourceIriFromBulkResponse(bulkResponse: JsObject, clientID: String): String = { val resIriOption: Option[JsValue] = bulkResponse.fields.get("createdResources") match { case Some(createdResources: JsArray) => @@ -447,12 +448,12 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV // the file first to a place which is shared with sipi val dest = FileUtil.createTempFile(settings, Some("jpg")) new FileOutputStream(dest) - .getChannel - .transferFrom( - new FileInputStream(fileToUpload).getChannel, - 0, - Long.MaxValue - ) + .getChannel + .transferFrom( + new FileInputStream(fileToUpload).getChannel, + 0, + Long.MaxValue + ) val absoluteFilePath = dest.getAbsolutePath @@ -725,12 +726,12 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV val gravsearchTemplateJSON: JsObject = getResponseJson(gravsearchTemplateRequest) - val gravsearchTemplateIri: IRI = gravsearchTemplateJSON.fields.get("res_id") match { + gravsearchTemplateIri.set(gravsearchTemplateJSON.fields.get("res_id") match { case Some(JsString(gravsearchIri)) => gravsearchIri case _ => throw InvalidApiJsonException("expected IRI for Gravsearch template") - } + }) // create an XSL transformation val headerParams = JsObject( @@ -774,7 +775,7 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV val letterTEIRequest: HttpRequest = Get(baseApiUrl + "/v2/tei/" + URLEncoder.encode(letterIri.get, "UTF-8") + "?textProperty=" + URLEncoder.encode("http://0.0.0.0:3333/ontology/0801/beol/v2#hasText", "UTF-8") + "&mappingIri=" + URLEncoder.encode("http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF/mappings/BEOLToTEI", "UTF-8") + - "&gravsearchTemplateIri=" + URLEncoder.encode(gravsearchTemplateIri, "UTF-8") + + "&gravsearchTemplateIri=" + URLEncoder.encode(gravsearchTemplateIri.get, "UTF-8") + "&teiHeaderXSLTIri=" + URLEncoder.encode(headerXSLTIri, "UTF-8") ) @@ -785,47 +786,47 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV val xmlExpected = s""" - | - | - | - | - | Testletter - | - | - |

This is the TEI/XML representation of the resource identified by the Iri - | ${letterIri.get}. - |

- |
- | - |

Representation of the resource's text as TEI/XML

- |
- |
- | - | - | - | Scheuchzer, - | Johann Jacob - | - | - | - | Hermann, - | Jacob - | - | - | - |
- | - | - |

[...] Viro Clarissimo.

- |

Dn. Jacobo Hermanno S. S. M. C.

- |

et Ph. M.

- |

S. P. D.

- |

J. J. Sch.

- |

En quae desideras, vir Erud.e κεχαρισμένω θυμῷ Actorum Lipsiensium fragmentaGemeint sind die im Brief Hermanns von 1703.06.05 erbetenen Exemplare AE Aprilis 1703 und AE Suppl., tom. III, 1702. animi mei erga te prope[n]sissimi tenuia indicia. Dudum est, ex quo Tibi innotescere, et tuam ambire amicitiam decrevi, dudum, ex quo Ingenij Tui acumen suspexi, immo non potui quin admirarer pro eo, quod summam Demonstrationem Tuam de Iride communicare dignatus fueris summas ago grates; quamvis in hoc studij genere, non alias [siquid] μετρικώτατος, propter aliorum negotiorum continuam seriem non altos possim scandere gradus. Perge Vir Clariss. Erudito orbi propalare Ingenij Tui fructum; sed et me amare.

- |

d. [10] Jun. 1703.Der Tag ist im Manuskript unleserlich. Da der Entwurf in Scheuchzers "Copiae epistolarum" zwischen zwei Einträgen vom 10. Juni 1703 steht, ist der Brief wohl auf den gleichen Tag zu datieren. - |

- |
- |
+ | + | + | + | + | Testletter + | + | + |

This is the TEI/XML representation of the resource identified by the Iri + | ${letterIri.get}. + |

+ |
+ | + |

Representation of the resource's text as TEI/XML

+ |
+ |
+ | + | + | + | Scheuchzer, + | Johann Jacob + | + | + | + | Hermann, + | Jacob + | + | + | + |
+ | + | + |

[...] Viro Clarissimo.

+ |

Dn. Jacobo Hermanno S. S. M. C.

+ |

et Ph. M.

+ |

S. P. D.

+ |

J. J. Sch.

+ |

En quae desideras, vir Erud.e κεχαρισμένω θυμῷ Actorum Lipsiensium fragmentaGemeint sind die im Brief Hermanns von 1703.06.05 erbetenen Exemplare AE Aprilis 1703 und AE Suppl., tom. III, 1702. animi mei erga te prope[n]sissimi tenuia indicia. Dudum est, ex quo Tibi innotescere, et tuam ambire amicitiam decrevi, dudum, ex quo Ingenij Tui acumen suspexi, immo non potui quin admirarer pro eo, quod summam Demonstrationem Tuam de Iride communicare dignatus fueris summas ago grates; quamvis in hoc studij genere, non alias [siquid] μετρικώτατος, propter aliorum negotiorum continuam seriem non altos possim scandere gradus. Perge Vir Clariss. Erudito orbi propalare Ingenij Tui fructum; sed et me amare.

+ |

d. [10] Jun. 1703.Der Tag ist im Manuskript unleserlich. Da der Entwurf in Scheuchzers "Copiae epistolarum" zwischen zwei Einträgen vom 10. Juni 1703 steht, ist der Brief wohl auf den gleichen Tag zu datieren. + |

+ |
+ |
""".stripMargin val xmlDiff: Diff = DiffBuilder.compare(Input.fromString(letterResponseBodyXML)).withTest(Input.fromString(xmlExpected)).build() @@ -833,7 +834,23 @@ class KnoraSipiIntegrationV1ITSpec extends ITKnoraLiveSpec(KnoraSipiIntegrationV xmlDiff.hasDifferences should be(false) } - } -} + "provide a helpful error message if an XSLT file is not found" in { + val missingHeaderXSLTIri = "http://rdfh.ch/0801/608NfPLCRpeYnkXKABC5mg" + val letterTEIRequest: HttpRequest = Get(baseApiUrl + "/v2/tei/" + URLEncoder.encode(letterIri.get, "UTF-8") + + "?textProperty=" + URLEncoder.encode("http://0.0.0.0:3333/ontology/0801/beol/v2#hasText", "UTF-8") + + "&mappingIri=" + URLEncoder.encode("http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF/mappings/BEOLToTEI", "UTF-8") + + "&gravsearchTemplateIri=" + URLEncoder.encode(gravsearchTemplateIri.get, "UTF-8") + + "&teiHeaderXSLTIri=" + URLEncoder.encode(missingHeaderXSLTIri, "UTF-8") + ) + + val response: HttpResponse = singleAwaitingRequest(letterTEIRequest) + assert(response.status.intValue == 500) + val responseBodyStr: String = Await.result(response.entity.toStrict(2.seconds).map(_.data.decodeString("UTF-8")), 2.seconds) + assert(responseBodyStr.contains("Unable to get file")) + assert(responseBodyStr.contains("as requested by org.knora.webapi.responders.v2.StandoffResponderV2")) + assert(responseBodyStr.contains("Sipi responded with HTTP status code 404")) + } + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/RequestWithSender.scala b/webapi/src/main/scala/org/knora/webapi/messages/RequestWithSender.scala new file mode 100644 index 0000000000..3c23cc459f --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/RequestWithSender.scala @@ -0,0 +1,27 @@ +/* + * Copyright © 2015-2018 the contributors (see Contributors.md). + * + * This file is part of Knora. + * + * Knora is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Knora is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Knora. If not, see . + */ + +package org.knora.webapi.messages + +/** + * A request message that knows the name of the sender. + */ +trait RequestWithSender { + val senderName: String +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala b/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala index a124f1fee1..c559322df7 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala @@ -23,6 +23,7 @@ import java.io.File import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import org.knora.webapi._ +import org.knora.webapi.messages.RequestWithSender import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.StoreRequest import org.knora.webapi.messages.v1.responder.usermessages.UserProfileV1 @@ -379,7 +380,8 @@ case class DeleteTemporaryFileRequestV2(internalFilename: String, * @param requestingUser the user making the request. */ case class SipiGetTextFileRequest(fileUrl: String, - requestingUser: UserADM) extends SipiRequestV2 + requestingUser: UserADM, + senderName: String) extends SipiRequestV2 with RequestWithSender /** * Represents a response for [[SipiGetTextFileRequest]]. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala index d1126d14da..02ee7415f8 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala @@ -1327,7 +1327,11 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt for { gravsearchTemplateUrl <- recoveredGravsearchUrlFuture - response: SipiGetTextFileResponse <- (storeManager ? SipiGetTextFileRequest(fileUrl = gravsearchTemplateUrl, KnoraSystemInstances.Users.SystemUser)).mapTo[SipiGetTextFileResponse] + response: SipiGetTextFileResponse <- (storeManager ? SipiGetTextFileRequest( + fileUrl = gravsearchTemplateUrl, + requestingUser = KnoraSystemInstances.Users.SystemUser, + senderName = this.getClass.getName + )).mapTo[SipiGetTextFileResponse] gravsearchTemplate: String = response.content } yield gravsearchTemplate diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala index 93946813c9..6b623f8022 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala @@ -52,16 +52,16 @@ import scala.concurrent.{ExecutionContext, Future} import scala.xml.{Elem, Node, NodeSeq, XML} /** - * Responds to requests relating to the creation of mappings from XML elements and attributes to standoff classes and properties. - */ + * Responds to requests relating to the creation of mappings from XML elements and attributes to standoff classes and properties. + */ class StandoffResponderV2(responderData: ResponderData) extends Responder(responderData) { /* actor materializer needed for http requests */ implicit val materializer: ActorMaterializer = ActorMaterializer() /** - * Receives a message of type [[StandoffResponderRequestV2]], and returns an appropriate response message. - */ + * Receives a message of type [[StandoffResponderRequestV2]], and returns an appropriate response message. + */ def receive(msg: StandoffResponderRequestV2) = msg match { case getStandoffPageRequestV2: GetStandoffPageRequestV2 => getStandoffV2(getStandoffPageRequestV2) case getRemainingStandoffFromTextValueRequestV2: GetRemainingStandoffFromTextValueRequestV2 => getRemainingStandoffFromTextValueV2(getRemainingStandoffFromTextValueRequestV2) @@ -145,12 +145,12 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * If not already in the cache, retrieves a `knora-base:XSLTransformation` in the triplestore and requests the corresponding XSL transformation file from Sipi. - * - * @param xslTransformationIri the IRI of the resource representing the XSL Transformation (a [[OntologyConstants.KnoraBase.XSLTransformation]]). - * @param requestingUser the user making the request. - * @return a [[GetXSLTransformationResponseV2]]. - */ + * If not already in the cache, retrieves a `knora-base:XSLTransformation` in the triplestore and requests the corresponding XSL transformation file from Sipi. + * + * @param xslTransformationIri the IRI of the resource representing the XSL Transformation (a [[OntologyConstants.KnoraBase.XSLTransformation]]). + * @param requestingUser the user making the request. + * @return a [[GetXSLTransformationResponseV2]]. + */ private def getXSLTransformation(xslTransformationIri: IRI, requestingUser: UserADM): Future[GetXSLTransformationResponseV2] = { val xsltUrlFuture = for { @@ -201,7 +201,11 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon Future(xsltMaybe.get) } else { for { - response: SipiGetTextFileResponse <- (storeManager ? SipiGetTextFileRequest(fileUrl = xsltFileUrl, KnoraSystemInstances.Users.SystemUser)).mapTo[SipiGetTextFileResponse] + response: SipiGetTextFileResponse <- (storeManager ? SipiGetTextFileRequest( + fileUrl = xsltFileUrl, + requestingUser = KnoraSystemInstances.Users.SystemUser, + senderName = this.getClass.getName + )).mapTo[SipiGetTextFileResponse] _ = CacheUtil.put(cacheName = xsltCacheName, key = xsltFileUrl, value = response.content) } yield response.content } @@ -212,12 +216,12 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon /** - * Creates a mapping between XML elements and attributes to standoff classes and properties. - * The mapping is used to convert XML documents to texts with standoff and back. - * - * @param xml the provided mapping. - * @param requestingUser the client that made the request. - */ + * Creates a mapping between XML elements and attributes to standoff classes and properties. + * The mapping is used to convert XML documents to texts with standoff and back. + * + * @param xml the provided mapping. + * @param requestingUser the client that made the request. + */ private def createMappingV2(xml: String, label: String, projectIri: SmartIri, mappingName: String, requestingUser: UserADM, apiRequestID: UUID): Future[CreateMappingResponseV2] = { def createMappingAndCheck(xml: String, label: String, mappingIri: IRI, namedGraph: String, requestingUser: UserADM): Future[CreateMappingResponseV2] = { @@ -446,12 +450,12 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * Transforms a mapping represented as a Seq of [[MappingElement]] to a [[MappingXMLtoStandoff]]. - * This method is called when reading a mapping back from the triplestore. - * - * @param mappingElements the Seq of MappingElement to be transformed. - * @return a [[MappingXMLtoStandoff]]. - */ + * Transforms a mapping represented as a Seq of [[MappingElement]] to a [[MappingXMLtoStandoff]]. + * This method is called when reading a mapping back from the triplestore. + * + * @param mappingElements the Seq of MappingElement to be transformed. + * @return a [[MappingXMLtoStandoff]]. + */ private def transformMappingElementsToMappingXMLtoStandoff(mappingElements: Seq[MappingElement], defaultXSLTransformation: Option[IRI]): MappingXMLtoStandoff = { val mappingXMLToStandoff = mappingElements.foldLeft(MappingXMLtoStandoff(namespace = Map.empty[String, Map[String, Map[String, XMLTag]]], defaultXSLTransformation = None)) { @@ -560,17 +564,17 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * The name of the mapping cache. - */ + * The name of the mapping cache. + */ val mappingCacheName = "mappingCache" /** - * Gets a mapping either from the cache or by making a request to the triplestore. - * - * @param mappingIri the IRI of the mapping to retrieve. - * @param requestingUser the user making the request. - * @return a [[MappingXMLtoStandoff]]. - */ + * Gets a mapping either from the cache or by making a request to the triplestore. + * + * @param mappingIri the IRI of the mapping to retrieve. + * @param requestingUser the user making the request. + * @return a [[MappingXMLtoStandoff]]. + */ private def getMappingV2(mappingIri: IRI, requestingUser: UserADM): Future[GetMappingResponseV2] = { val mappingFuture: Future[GetMappingResponseV2] = CacheUtil.get[MappingXMLtoStandoff](cacheName = mappingCacheName, key = mappingIri) match { @@ -612,13 +616,13 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * - * Gets a mapping from the triplestore. - * - * @param mappingIri the IRI of the mapping to retrieve. - * @param requestingUser the user making the request. - * @return a [[MappingXMLtoStandoff]]. - */ + * + * Gets a mapping from the triplestore. + * + * @param mappingIri the IRI of the mapping to retrieve. + * @param requestingUser the user making the request. + * @return a [[MappingXMLtoStandoff]]. + */ private def getMappingFromTriplestore(mappingIri: IRI, requestingUser: UserADM): Future[MappingXMLtoStandoff] = { val getMappingSparql = queries.sparql.v2.txt.getMapping( @@ -711,12 +715,12 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * Gets the required standoff entities (classes and properties) from the mapping and requests information about these entities from the ontology responder. - * - * @param mappingXMLtoStandoff the mapping to be used. - * @param requestingUser the client that made the request. - * @return a [[StandoffEntityInfoGetResponseV2]] holding information about standoff classes and properties. - */ + * Gets the required standoff entities (classes and properties) from the mapping and requests information about these entities from the ontology responder. + * + * @param mappingXMLtoStandoff the mapping to be used. + * @param requestingUser the client that made the request. + * @return a [[StandoffEntityInfoGetResponseV2]] holding information about standoff classes and properties. + */ private def getStandoffEntitiesFromMappingV2(mappingXMLtoStandoff: MappingXMLtoStandoff, requestingUser: UserADM): Future[StandoffEntityInfoGetResponseV2] = { implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance @@ -819,29 +823,29 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * A [[TaskResult]] containing a page of standoff queried from a text value. - * - * @param underlyingResult the underlying standoff result. - * @param nextTask the next task, or `None` if there is no more standoff to query in the text value. - */ + * A [[TaskResult]] containing a page of standoff queried from a text value. + * + * @param underlyingResult the underlying standoff result. + * @param nextTask the next task, or `None` if there is no more standoff to query in the text value. + */ case class StandoffTaskResult(underlyingResult: StandoffTaskUnderlyingResult, nextTask: Option[GetStandoffTask]) extends TaskResult[StandoffTaskUnderlyingResult] /** - * The underlying result type contained in a [[StandoffTaskResult]]. - * - * @param standoff the standoff that was queried. - */ + * The underlying result type contained in a [[StandoffTaskResult]]. + * + * @param standoff the standoff that was queried. + */ case class StandoffTaskUnderlyingResult(standoff: Vector[StandoffTagV2]) /** - * A task that gets a page of standoff from a text value. - * - * @param resourceIri the IRI of the resource containing the value. - * @param valueIri the IRI of the value. - * @param offset the start index of the first standoff tag to be returned. - * @param requestingUser the user making the request. - */ + * A task that gets a page of standoff from a text value. + * + * @param resourceIri the IRI of the resource containing the value. + * @param valueIri the IRI of the value. + * @param offset the start index of the first standoff tag to be returned. + * @param requestingUser the user making the request. + */ case class GetStandoffTask(resourceIri: IRI, valueIri: IRI, offset: Int, @@ -882,11 +886,11 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon } /** - * Returns all pages of standoff markup from a text value, except for the first page. - * - * @param getRemainingStandoffFromTextValueRequestV2 the request message. - * @return the text value's standoff markup. - */ + * Returns all pages of standoff markup from a text value, except for the first page. + * + * @param getRemainingStandoffFromTextValueRequestV2 the request message. + * @return the text value's standoff markup. + */ private def getRemainingStandoffFromTextValueV2(getRemainingStandoffFromTextValueRequestV2: GetRemainingStandoffFromTextValueRequestV2): Future[GetStandoffResponseV2] = { val firstTask = GetStandoffTask( resourceIri = getRemainingStandoffFromTextValueRequestV2.resourceIri, diff --git a/webapi/src/main/scala/org/knora/webapi/store/StoreManager.scala b/webapi/src/main/scala/org/knora/webapi/store/StoreManager.scala index 0f4b42d16c..f4c38e7332 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/StoreManager.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/StoreManager.scala @@ -53,22 +53,22 @@ class StoreManager extends Actor with ActorLogging { /** * The Knora settings. */ - protected val settings = Settings(system) + protected val settings: SettingsImpl = Settings(system) /** * Starts the Triplestore Manager Actor */ - protected lazy val triplestoreManager = makeActor(Props(new TriplestoreManager with LiveActorMaker).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), TriplestoreManagerActorName) + protected lazy val triplestoreManager: ActorRef = makeActor(Props(new TriplestoreManager with LiveActorMaker).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), TriplestoreManagerActorName) /** * Starts the IIIF Manager Actor */ - protected lazy val iiifManager = makeActor(Props(new IIIFManager with LiveActorMaker).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), IIIFManagerActorName) + protected lazy val iiifManager: ActorRef = makeActor(Props(new IIIFManager with LiveActorMaker).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), IIIFManagerActorName) /** * Instantiates the Redis Manager */ - protected lazy val redisManager = makeActor(Props(new CacheServiceManager).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), RedisManagerActorName) + protected lazy val redisManager: ActorRef = makeActor(Props(new CacheServiceManager).withDispatcher(KnoraDispatchers.KnoraActorDispatcher), RedisManagerActorName) def receive = LoggingReceive { case tripleStoreMessage: TriplestoreRequest => triplestoreManager forward tripleStoreMessage diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/SipiConnector.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/SipiConnector.scala index 80cc84b785..504e581d99 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/SipiConnector.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/SipiConnector.scala @@ -30,7 +30,6 @@ import org.apache.http.impl.client.{CloseableHttpClient, HttpClients} import org.apache.http.message.BasicNameValuePair import org.apache.http.util.EntityUtils import org.apache.http.{Consts, HttpHost, HttpRequest, NameValuePair} -import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.sipimessages.GetFileMetadataResponseV2JsonProtocol._ import org.knora.webapi.messages.store.sipimessages.RepresentationV1JsonProtocol._ import org.knora.webapi.messages.store.sipimessages.SipiConstants.FileType @@ -47,8 +46,8 @@ import scala.concurrent.ExecutionContext import scala.util.Try /** - * Makes requests to Sipi. - */ + * Makes requests to Sipi. + */ class SipiConnector extends Actor with ActorLogging { implicit val system: ActorSystem = context.system @@ -63,10 +62,10 @@ class SipiConnector extends Actor with ActorLogging { private val sipiTimeoutMillis = settings.sipiTimeout.toMillis.toInt private val sipiRequestConfig = RequestConfig.custom() - .setConnectTimeout(sipiTimeoutMillis) - .setConnectionRequestTimeout(sipiTimeoutMillis) - .setSocketTimeout(sipiTimeoutMillis) - .build() + .setConnectTimeout(sipiTimeoutMillis) + .setConnectionRequestTimeout(sipiTimeoutMillis) + .setSocketTimeout(sipiTimeoutMillis) + .build() private val httpClient: CloseableHttpClient = HttpClients.custom.setDefaultRequestConfig(sipiRequestConfig).build @@ -76,17 +75,17 @@ class SipiConnector extends Actor with ActorLogging { case getFileMetadataRequestV2: GetFileMetadataRequestV2 => try2Message(sender(), getFileMetadataV2(getFileMetadataRequestV2), log) case moveTemporaryFileToPermanentStorageRequestV2: MoveTemporaryFileToPermanentStorageRequestV2 => try2Message(sender(), moveTemporaryFileToPermanentStorageV2(moveTemporaryFileToPermanentStorageRequestV2), log) case deleteTemporaryFileRequestV2: DeleteTemporaryFileRequestV2 => try2Message(sender(), deleteTemporaryFileV2(deleteTemporaryFileRequestV2), log) - case SipiGetTextFileRequest(fileUrl, requestingUser) => try2Message(sender(), sipiGetXsltTransformationRequestV2(fileUrl, requestingUser), log) + case getTextFileRequest: SipiGetTextFileRequest => try2Message(sender(), sipiGetTextFileRequestV2(getTextFileRequest), log) case IIIFServiceGetStatus => try2Message(sender(), iiifGetStatus(), log) case other => handleUnexpectedMessage(sender(), other, log, this.getClass.getName) } /** - * Convert a file that has been sent to Knora (non GUI-case). - * - * @param conversionRequest the information about the file (uploaded by Knora). - * @return a [[SipiConversionResponseV1]] representing the file values to be added to the triplestore. - */ + * Convert a file that has been sent to Knora (non GUI-case). + * + * @param conversionRequest the information about the file (uploaded by Knora). + * @return a [[SipiConversionResponseV1]] representing the file values to be added to the triplestore. + */ private def convertPathV1(conversionRequest: SipiConversionPathRequestV1): Try[SipiConversionResponseV1] = { val url = s"${settings.internalSipiImageConversionUrlV1}/${settings.sipiPathConversionRouteV1}" @@ -94,11 +93,11 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Convert a file that is already managed by Sipi (GUI-case). - * - * @param conversionRequest the information about the file (managed by Sipi). - * @return a [[SipiConversionResponseV1]] representing the file values to be added to the triplestore. - */ + * Convert a file that is already managed by Sipi (GUI-case). + * + * @param conversionRequest the information about the file (managed by Sipi). + * @return a [[SipiConversionResponseV1]] representing the file values to be added to the triplestore. + */ private def convertFileV1(conversionRequest: SipiConversionFileRequestV1): Try[SipiConversionResponseV1] = { val url = s"${settings.internalSipiImageConversionUrlV1}/${settings.sipiFileConversionRouteV1}" @@ -106,13 +105,13 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Makes a conversion request to Sipi and creates a [[SipiConversionResponseV1]] - * containing the file values to be added to the triplestore. - * - * @param urlPath the Sipi route to be called. - * @param conversionRequest the message holding the information to make the request. - * @return a [[SipiConversionResponseV1]]. - */ + * Makes a conversion request to Sipi and creates a [[SipiConversionResponseV1]] + * containing the file values to be added to the triplestore. + * + * @param urlPath the Sipi route to be called. + * @param conversionRequest the message holding the information to make the request. + * @return a [[SipiConversionResponseV1]]. + */ private def callSipiConvertRoute(urlPath: String, conversionRequest: SipiConversionRequestV1): Try[SipiConversionResponseV1] = { val context: HttpClientContext = HttpClientContext.create @@ -232,11 +231,11 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Asks Sipi for metadata about a file. - * - * @param getFileMetadataRequestV2 the request. - * @return a [[GetFileMetadataResponseV2]] containing the requested metadata. - */ + * Asks Sipi for metadata about a file. + * + * @param getFileMetadataRequestV2 the request. + * @return a [[GetFileMetadataResponseV2]] containing the requested metadata. + */ private def getFileMetadataV2(getFileMetadataRequestV2: GetFileMetadataRequestV2): Try[GetFileMetadataResponseV2] = { val knoraInfoUrl = getFileMetadataRequestV2.fileUrl + "/knora.json" @@ -248,11 +247,11 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Asks Sipi to move a file from temporary storage to permanent storage. - * - * @param moveTemporaryFileToPermanentStorageRequestV2 the request. - * @return a [[SuccessResponseV2]]. - */ + * Asks Sipi to move a file from temporary storage to permanent storage. + * + * @param moveTemporaryFileToPermanentStorageRequestV2 the request. + * @return a [[SuccessResponseV2]]. + */ private def moveTemporaryFileToPermanentStorageV2(moveTemporaryFileToPermanentStorageRequestV2: MoveTemporaryFileToPermanentStorageRequestV2): Try[SuccessResponseV2] = { val token: String = JWTHelper.createToken( userIri = moveTemporaryFileToPermanentStorageRequestV2.requestingUser.id, @@ -284,11 +283,11 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Asks Sipi to delete a temporary file. - * - * @param deleteTemporaryFileRequestV2 the request. - * @return a [[SuccessResponseV2]]. - */ + * Asks Sipi to delete a temporary file. + * + * @param deleteTemporaryFileRequestV2 the request. + * @return a [[SuccessResponseV2]]. + */ private def deleteTemporaryFileV2(deleteTemporaryFileRequestV2: DeleteTemporaryFileRequestV2): Try[SuccessResponseV2] = { val token: String = JWTHelper.createToken( userIri = deleteTemporaryFileRequestV2.requestingUser.id, @@ -313,22 +312,26 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Asks Sipi for a file. - * @param xsltFileUrl the file's URL. - * @param requestingUser the user making the request. - */ - private def sipiGetXsltTransformationRequestV2(xsltFileUrl: String, requestingUser: UserADM): Try[SipiGetTextFileResponse] = { - // ask Sipi to return the XSL transformation - val request = new HttpGet(xsltFileUrl) - - for { - responseStr <- doSipiRequest(request) + * Asks Sipi for a text file used internally by Knora. + * + * @param textFileRequest the request message. + */ + private def sipiGetTextFileRequestV2(textFileRequest: SipiGetTextFileRequest): Try[SipiGetTextFileResponse] = { + val httpRequest = new HttpGet(textFileRequest.fileUrl) + + val sipiResponseTry: Try[SipiGetTextFileResponse] = for { + responseStr <- doSipiRequest(httpRequest) } yield SipiGetTextFileResponse(responseStr) + + sipiResponseTry.recover { + case badRequestException: BadRequestException => throw SipiException(s"Unable to get file ${textFileRequest.fileUrl} from Sipi as requested by ${textFileRequest.senderName}: ${badRequestException.message}") + case sipiException: SipiException => throw SipiException(s"Unable to get file ${textFileRequest.fileUrl} from Sipi as requested by ${textFileRequest.senderName}: ${sipiException.message}", sipiException.cause) + } } /** - * Tries to access the IIIF Service. - */ + * Tries to access the IIIF Service. + */ private def iiifGetStatus(): Try[IIIFServiceStatusResponse] = { val request = new HttpGet(settings.internalSipiBaseUrl + "/server/test.html") @@ -341,11 +344,11 @@ class SipiConnector extends Actor with ActorLogging { } /** - * Makes an HTTP request to Sipi and returns the response. - * - * @param request the HTTP request. - * @return Sipi's response. - */ + * Makes an HTTP request to Sipi and returns the response. + * + * @param request the HTTP request. + * @return Sipi's response. + */ private def doSipiRequest(request: HttpRequest): Try[String] = { val httpContext: HttpClientContext = HttpClientContext.create