Commit d684c058 authored by Günter Hipler's avatar Günter Hipler
Browse files

first stept refactoring for validation

parent 57db067a
Pipeline #13420 passed with stage
in 2 minutes and 31 seconds
......@@ -2,7 +2,7 @@ package controllers
import javax.inject.Inject
import modules.OaiRepository
import org.swissbib.memobase.oai.common.verb.OaiVerb
import org.swissbib.memobase.oai.common.validation.OaiQueryParameterChecker
import play.api.Configuration
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents, Rendering}
......@@ -25,22 +25,21 @@ class OaiController @Inject()(cc: ControllerComponents,
resumptionToken: Option[String]): Action[AnyContent] =
Action { implicit request =>
val mappedRequest: Map[String, Seq[String]] = request.queryString
val oaiRequest = OaiVerb.getOaiRequest(
val qP: Map[String, Seq[String]] = request.queryString
val userRequest = new OaiQueryParameterChecker(
verb = verb,
metadataPrefix = metadataPrefix,
set = set,
from = from,
until = until,
identifier = identifier,
resumptionToken = resumptionToken)
resumptionToken = resumptionToken,
allQueryParameter = qP).checkQueryParameter
val response = userRequest.getRunner(config, repository).run().createResponse
val response = oaiRequest.getRunner(config, repository).run().createResponse
/*
todo: make Writable for Node[Seq]
todo: make Writable for Node[Seq] so toString isn't necessary
[info] Compiling 4 Scala sources to /home/swissbib/environment/code/swissbib.repositories/memoriav/gitlab/services/oai/target/scala-2.13/classes ...
[error] /home/swissbib/environment/code/swissbib.repositories/memoriav/gitlab/services/oai/app/controllers/OaiController.scala:44:9: Cannot write an instance of Seq[scala.xml.Node] to HTTP response. Try to define a Writeable[Seq[scala.xml.Node]]
[error] Ok(response.createResponse).as("text/xml")
......
package org.swissbib.memobase.oai.common.validation
import org.swissbib.memobase.oai.common.verb.OaiVerb
import org.swissbib.memobase.oai.common.verb.OaiVerb.{ListSets, OaiVerb}
import org.swissbib.memobase.oai.request.{BadArgumentsReq, BaseUserRequest, SystemErrorReq}
import scala.util.{Failure, Success}
case class OaiCheckedVerbWithParameter(verb:Option[OaiVerb],
metadataPrefix: Option[String],
set: Option[String],
from:Option[String],
until:Option[String],
identifier: Option[String],
resumptionToken: Option[String],
allQueryParameter: Map[String, Seq[String]])
class OaiQueryParameterChecker(verb: Option[String],
metadataPrefix: Option[String],
set: Option[String],
from:Option[String],
until:Option[String],
identifier: Option[String],
resumptionToken: Option[String],
allQueryParameter: Map[String, Seq[String]]) {
def checkQueryParameter: BaseUserRequest = {
OaiVerb.getVerb(verb) match {
case Success(value) =>
value match {
case OaiVerb.GetRecord => GetRecordValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.Identify => IdentifyValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.ListIdentifiers => ListIdentifiersValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.ListMetadataFormats => ListMetadataFormatsValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.ListRecords => ListRecordsValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.ListSets => ListSetsValidation(
OaiCheckedVerbWithParameter(Some(value),metadataPrefix,set,from, until, identifier, resumptionToken, allQueryParameter))
case OaiVerb.WrongVerb | OaiVerb.MissingVerb =>
//it is possible that users send these not allowed enum values
//only used as part of the type to round up the workflow
BadArgumentsReq(BadArgumentsParameter(Map("badVerb" -> s"Invalid verb $verb in request")))
}
case Failure(_:MissingParameterException) =>
//todo: see WrongVerb or MissingVerb
BadArgumentsReq(BadArgumentsParameter(Map("badVerb" -> s"missing verb in request")))
case Failure(_: NoSuchElementException) =>
//todo: see WrongVerb or MissingVerb
BadArgumentsReq(BadArgumentsParameter(Map("badVerb" -> s"Invalid verb $verb in request")))
case Failure(exception) =>
SystemErrorReq(exception)
}
}
}
class MissingParameterException(message: String) extends Exception(message)
package org.swissbib.memobase.oai.common.validation
import org.swissbib.memobase.oai.common.verb.OaiVerb.{GetRecord, Identify, ListIdentifiers, ListMetadataFormats,
ListRecords, ListSets, OaiVerb}
sealed abstract class OaiParameterBase() extends Product with Serializable {
def listRequestParameters: Map[String,String]
}
case class ParameterToCheck(verb:Option[OaiVerb],
metadataPrefix: Option[String],
set: Option[String],
from:Option[String],
until:Option[String],
identifier: Option[String],
resumptionToken: Option[String],
allQueryParameter: Map[String, Seq[String]]) extends OaiParameterBase {
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedIdentifyParameter (verb:OaiVerb) extends OaiParameterBase {
require(verb == Identify)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedGetRecordParameter (verb:OaiVerb, identifier: String, metadataPrefix: String) extends OaiParameterBase {
require(verb == GetRecord)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedListIdentifiersParameter (verb:OaiVerb, metadataPrefix: String, set:Option[String],
from:Option[String], until:Option[String]) extends OaiParameterBase {
require(verb == ListIdentifiers)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedListIdentifiersParameterExclusive (verb:OaiVerb, resumptionToken: String) extends OaiParameterBase {
require(verb == ListIdentifiers)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedListMetadaFormatsParameter(verb:OaiVerb, identifier: Option[String]) extends OaiParameterBase {
require(verb == ListMetadataFormats)
override def listRequestParameters: Map[String, String] = Map()
}
final case class CheckedListRecordsParameter (verb:OaiVerb,
metadataPrefix: String,
from: Option[String],
until: Option[String],
set: Option[String]) extends OaiParameterBase {
require(verb == ListRecords)
override def listRequestParameters: Map[String, String] = Map()
}
final case class CheckedListRecordsParameterExclusive (verb:OaiVerb, resumptionToken: String) extends OaiParameterBase {
require(verb == ListRecords)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedListSetsParameterExclusive (verb:OaiVerb, resumptionToken: String) extends OaiParameterBase {
require(verb == ListSets)
override def listRequestParameters: Map[String, String] = Map()
}
case class CheckedListSetsParameter (verb:OaiVerb) extends OaiParameterBase {
require(verb == ListSets)
override def listRequestParameters: Map[String, String] = Map()
}
case class BadArgumentsParameter (badArguments: Map[String,String]) extends OaiParameterBase {
//require on verb is not possible because could be any out of the Enums type
override def listRequestParameters: Map[String, String] = Map()
}
package org.swissbib.memobase.oai.common.validation
import org.swissbib.memobase.oai.request.{BadArgumentsReq, BaseUserRequest, IdentifyReq}
private object AllowedParameter {
val identify = List("verb")
}
abstract sealed class ParameterValidationFunction () extends Function1[OaiCheckedVerbWithParameter, BaseUserRequest] {
def checkParameterByName(paramsInReq: Map[String, Seq[String]], allowedList:List[String]) = {
paramsInReq.keySet.diff(allowedList.toSet)
}
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest
}
object GetRecordValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = ???
}
object ListRecordsValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = ???
}
object ListSetsValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = ???
}
object IdentifyValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = {
/*
Error and Exception Conditions
badArgument - The request includes illegal arguments.
Response Format
The response must include one instance of the following elements:
repositoryName : a human readable name for the repository;
baseURL : the base URL of the repository;
protocolVersion : the version of the OAI-PMH supported by the repository;
earliestDatestamp : a UTCdatetime that is the guaranteed lower limit of all datestamps recording changes, modifications, or deletions in the repository. A repository must not use datestamps lower than the one specified by the content of the earliestDatestamp element. earliestDatestamp must be expressed at the finest granularity supported by the repository.
deletedRecord : the manner in which the repository supports the notion of deleted records. Legitimate values are no ; transient ; persistent with meanings defined in the section on deletion.
granularity: the finest harvesting granularity supported by the repository. The legitimate values are YYYY-MM-DD and YYYY-MM-DDThh:mm:ssZ with meanings as defined in ISO8601.
*/
//no additional arguments allowed
v1 match {
case OaiCheckedVerbWithParameter(verb,None,None, None,None,None,None,allParameter) =>
val diffSetParams = checkParameterByName(allParameter,AllowedParameter.identify)
if (diffSetParams.nonEmpty)
BadArgumentsReq(BadArgumentsParameter(diffSetParams.map(key => (key,key)).toMap))
else
IdentifyReq(CheckedIdentifyParameter(verb.get))
case OaiCheckedVerbWithParameter(verb,_,_, _,_,_,_,allParameter) =>
val diffSetParams = checkParameterByName(allParameter,AllowedParameter.identify)
if (diffSetParams.nonEmpty)
BadArgumentsReq(BadArgumentsParameter(diffSetParams.map(key => (key,key)).toMap))
else
IdentifyReq(CheckedIdentifyParameter(verb.get))
}
}
//OaiRequest()
}
object ListIdentifiersValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = ???
}
object ListMetadataFormatsValidation extends ParameterValidationFunction {
override def apply(v1: OaiCheckedVerbWithParameter): BaseUserRequest = ???
}
package org.swissbib.memobase.oai.common.verb
import org.swissbib.memobase.oai.common.validation.{MissingQueryParameter, SystemFailureRequestValidation, WrongQueryParameterValue}
import org.swissbib.memobase.oai.request.{OaiRequest, UserRequest, WrongOaiRequest}
import org.swissbib.memobase.oai.common.validation.{MissingParameterException, MissingQueryParameter, SystemFailureRequestValidation, WrongQueryParameterValue}
import scala.util.{Failure, Success, Try}
import scala.util.{Try}
object OaiVerb extends Enumeration {
......@@ -13,7 +12,9 @@ object OaiVerb extends Enumeration {
//https://www.scala-lang.org/api/current/scala/Enumeration.html
}
import scala.language.implicitConversions
implicit def getVerb(x: Value): String = x.asInstanceOf[OaiVal].verb
val ListRecords: OaiVal = OaiVal("ListRecords")
......@@ -22,42 +23,14 @@ object OaiVerb extends Enumeration {
val GetRecord: OaiVal = OaiVal("GetRecord")
val ListMetadataFormats: OaiVal = OaiVal("ListMetadataFormats")
val Identify: OaiVal = OaiVal("Identify")
val WrongVerb: OaiVal = OaiVal("WrongVerb")
val MissingVerb: OaiVal = OaiVal("MissingVerb")
def getOaiRequest(verb: Option[String],
metadataPrefix: Option[String],
set: Option[String],
from:Option[String],
until:Option[String],
identifier: Option[String],
resumptionToken: Option[String],
allQueryParameter: Map[String, Seq[String]]): UserRequest = {
def getVerb(verb: Option[String]): Try[OaiVerb] = {
Try[OaiVerb](OaiVerb.withName(verb.getOrElse(throw
new MissingParameterException("mandatory parameter verb is missing")))) match {
case Success(v) => OaiRequest(
Some(v),
metadataPrefix,
set,
from,
until,
identifier,
resumptionToken
)
case Failure(_:MissingParameterException) =>
WrongOaiRequest(MissingQueryParameter("verb","query parameter verb is missing"))
case Failure(_: NoSuchElementException) =>
WrongOaiRequest(WrongQueryParameterValue("verb",s"value $verb.get for parameter verb is not allowed"))
case _ =>
WrongOaiRequest(SystemFailureRequestValidation("something really weird happened while validating the verb of the request")
)
}
new MissingParameterException("mandatory parameter verb is missing"))))
}
}
class MissingParameterException(message: String) extends Exception(message)
package org.swissbib.memobase.oai.request
import modules.OaiRepository
import org.swissbib.memobase.oai.common.validation.ValidationError
import org.swissbib.memobase.oai.response.OaiResponse
import org.swissbib.memobase.oai.common.verb.OaiVerb
import org.swissbib.memobase.oai.common.validation.{BadArgumentsParameter, CheckedGetRecordParameter, CheckedIdentifyParameter, CheckedListIdentifiersParameter, CheckedListIdentifiersParameterExclusive, CheckedListMetadaFormatsParameter, CheckedListRecordsParameter, CheckedListRecordsParameterExclusive, CheckedListSetsParameter, CheckedListSetsParameterExclusive, OaiParameterBase}
import org.swissbib.memobase.oai.common.verb.OaiVerb.OaiVerb
import org.swissbib.memobase.oai.runner.{GetExceptionRunner, GetRecordRunner, IdentifyRunner, ListIdentifiersRunner, ListMetadataFormatsRunner, ListRecordsRunner, ListSetsRunner, OaiRequestRunner}
import org.swissbib.memobase.oai.runner.{IdentifyRunner, OaiRequestRunner}
import play.api.Configuration
sealed abstract class BaseUserRequest {
def getRunner(config: Configuration, repository:OaiRepository): OaiRequestRunner
def parameterBase: OaiParameterBase
}
sealed abstract class UserRequest(val verb:Option[OaiVerb],
val metadataPrefix: Option[String],
val set: Option[String],
val from:Option[String],
val until:Option[String],
val identifier: Option[String],
val resumptionToken: Option[String]) {
val allQueryParameter: Map[String, Seq[String]]) {
def getRunner(config: Configuration, repository:OaiRepository): OaiRequestRunner
}
case class OaiRequest(override val verb:Option[OaiVerb],
override val metadataPrefix: Option[String],
override val set: Option[String],
override val from:Option[String],
override val until:Option[String],
override val identifier: Option[String],
override val resumptionToken: Option[String]) extends UserRequest(verb,
metadataPrefix,
set,
from,
until,
identifier,
resumptionToken) {
def hasNecessaryArguments:Boolean = {
def checkArgumentsGetRecord: Boolean = {
/*
https://www.openarchives.org/OAI/openarchivesprotocol.html#GetRecord
**identifier** a required argument that specifies the unique identifier of the item in the repository from which the record must be disseminated.
**metadataPrefix** a required argument that specifies the metadataPrefix of the format that should be included in the metadata part of the returned record . A record should only be returned if the format specified by the metadataPrefix can be disseminated from the item identified by the value of the identifier argument. The metadata formats supported by a repository and for a particular record can be retrieved using the ListMetadataFormats request.
Error and Exception Condi tions
badArgument - The request includes illegal arguments or is missing required arguments.
cannotDisseminateFormat - The value of the metadataPrefix argument is not supported by the item identified by the value of the identifier argument.
idDoesNotExist - The value of the identifier argument is unknown or illegal in this repository.
*/
this match {
case OaiRequest(_,Some(metadataPrefix),None, None,None,Some(ident),None) => true
case _ => false
}
}
def checkArgumentsIdentify: Boolean = {
/*
Error and Exception Conditions
badArgument - The request includes illegal arguments.
Response Format
The response must include one instance of the following elements:
repositoryName : a human readable name for the repository;
baseURL : the base URL of the repository;
protocolVersion : the version of the OAI-PMH supported by the repository;
earliestDatestamp : a UTCdatetime that is the guaranteed lower limit of all datestamps recording changes, modifications, or deletions in the repository. A repository must not use datestamps lower than the one specified by the content of the earliestDatestamp element. earliestDatestamp must be expressed at the finest granularity supported by the repository.
deletedRecord : the manner in which the repository supports the notion of deleted records. Legitimate values are no ; transient ; persistent with meanings defined in the section on deletion.
granularity: the finest harvesting granularity supported by the repository. The legitimate values are YYYY-MM-DD and YYYY-MM-DDThh:mm:ssZ with meanings as defined in ISO8601.
*/
//no additional arguments allowed
this match {
case OaiRequest(_,None,None, None,None,None,None) => true
case _ => false
}
}
def checkArgumentsListIdentifiers: Boolean = {
/*
https://www.openarchives.org/OAI/openarchivesprotocol.html#ListIdentifiers
Arguments
**from** an optional argument with a UTCdatetime value, which specifies a lower bound for datestamp-based selective harvesting.
**until** an optional argument with a UTCdatetime value, which specifies a upper bound for datestamp-based selective harvesting.
**metadataPrefix** a required argument, which specifies that headers should be returned only if the metadata format matching the supplied metadataPrefix is available or, depending on the repository's support for deletions, has been deleted. The metadata formats supported by a repository and for a particular item can be retrieved using the ListMetadataFormats request.
**set** an optional argument with a setSpec value , which specifies set criteria for selective harvesting.
**resumptionToken** an exclusive argument with a value that is the flow control token returned by a previous ListIdentifiers request that issued an incomplete list.
Error and Exception Conditions
badArgument - The request includes illegal arguments or is missing required arguments.
badResumptionToken - The value of the resumptionToken argument is invalid or expired.
cannotDisseminateFormat - The value of the metadataPrefix argument is not supported by the repository.
noRecordsMatch- The combination of the values of the from, until, and set arguments results in an empty list.
noSetHierarchy - The repository does not support sets.
*/
this match {
case OaiRequest(_,Some(metadataprefix),set, from,until,None,resumptionToken) => true
case _ => false
}
}
def checkArgumentsListMetadaFormats: Boolean = {
/*
Arguments
https://www.openarchives.org/OAI/openarchivesprotocol.html#ListMetadataFormats
identifier an optional argument that specifies the unique identifier of the item for which available metadata formats are being requested. If this argument is omitted, then the response includes all metadata formats supported by this repository. Note that the fact that a metadata format is supported by a repository does not mean that it can be disseminated from all items in the repository.
Error and Exception Conditions
badArgument - The request includes illegal arguments or is missing required arguments.
idDoesNotExist - The value of the identifier argument is unknown or illegal in this repository.
noMetadataFormats - There are no metadata formats available for the specified item.
*/
this match {
case OaiRequest(_,None,None, None,None,identifier,None) => true
case _ => false
}
}
def checkArgumentsListRecords: Boolean = {
/*
https://www.openarchives.org/OAI/openarchivesprotocol.html#ListRecords
Arguments
**from** an optional argument with a UTCdatetime value, which specifies a lower bound for datestamp-based selective harvesting.
**until** an optional argument with a UTCdatetime value, which specifies a upper bound for datestamp-based selective harvesting.
**set** an optional argument with a setSpec value , which specifies set criteria for selective harvesting.
**resumptionToken** an exclusive argument with a value that is the flow control token returned by a previous ListRecords request that issued an incomplete list.
**metadataPrefix** a required argument (unless the exclusive argument resumptionToken is used) that specifies the metadataPrefix of the format that should be included in the metadata part of the returned records. Records should be included only for items from which the metadata format
matching the metadataPrefix can be disseminated. The metadata formats supported by a repository and for a particular item can be retrieved using the ListMetadataFormats request.
Error and Exception Conditions
badArgument - The request includes illegal arguments or is missing required arguments.
badResumptionToken - The value of the resumptionToken argument is invalid or expired.
cannotDisseminateFormat - The value of the metadataPrefix argument is not supported by the repository.
noRecordsMatch - The combination of the values of the from, until, set and metadataPrefix arguments results in an empty list.
noSetHierarchy - The repository does not support sets.
*/
this match {
case OaiRequest(_,Some(mp),set, from,until,None,resumptiontoken) => true
case _ => false
}
}
def checkArgumentsListSets: Boolean = {
/*
https://www.openarchives.org/OAI/openarchivesprotocol.html#ListSets
Arguments
resumptionToken an exclusive argument with a value that is the flow control token returned by a previous ListSets request that issued an incomplete list.
GH: guess we do not need this for memobase because there aren't so much sets - probably
Error and Exception Conditions
badArgument - The request includes illegal arguments or is missing required arguments.
badResumptionToken - The value of the resumptionToken argument is invalid or expired.
noSetHierarchy - The repository does not support sets.
*/
this match {
case OaiRequest(_,None,None, None,None,None,resumptiontoken) => true
case _ => false
}
}
verb match {
case Some(OaiVerb.GetRecord) => println("Getrecord"); checkArgumentsGetRecord
case Some(OaiVerb.Identify) => println("Identify"); checkArgumentsIdentify
case Some(OaiVerb.ListIdentifiers) => println("ListIdentifieres"); checkArgumentsListIdentifiers
case Some(OaiVerb.ListMetadataFormats) => println("ListMetadataFormats"); checkArgumentsListMetadaFormats
case Some(OaiVerb.ListRecords) => println("ListRecords"); checkArgumentsListRecords
case Some(OaiVerb.ListSets) => println("ListSets"); checkArgumentsListSets
case Some(_) => println("illegal oai verb"); false
case None => println("verb is none"); false
}
}
//todo: validation for supplied argument
def areArgumentsValid: Boolean = true
override def getRunner (config: Configuration, repository:OaiRepository): OaiRequestRunner = {
//todo: too much imperative programming, make it more functional!
if (hasNecessaryArguments && areArgumentsValid) {
verb.get match {
case _: OaiVerb.GetRecord.type => GetRecordRunner(config, repository, this)
case _: OaiVerb.ListRecords.type => ListRecordsRunner(config, repository, this)
case _: OaiVerb.ListSets.type => ListSetsRunner(config, repository, this)
case _: OaiVerb.Identify.type => IdentifyRunner(config, repository, this)
case _: OaiVerb.ListIdentifiers.type => ListIdentifiersRunner(config, repository, this)
case _: OaiVerb.ListMetadataFormats.type => ListMetadataFormatsRunner(config, repository, this)
//case _ => IdentifyRunner(config, repository, this).run()
}
}
else {
//todo: create not legal runner
ListMetadataFormatsRunner(config, repository, this)
}
}
case class ListSetsReq(parameter: CheckedListSetsParameter) extends BaseUserRequest {
override def getRunner(config: Configuration, repository: OaiRepository):
OaiRequestRunner = ???
override def parameterBase: OaiParameterBase = parameter
}
case class ListSetsReqExclusive(parameter: CheckedListSetsParameterExclusive) extends BaseUserRequest {
override def getRunner(config: Configuration, repository: OaiRepository):
OaiRequestRunner = ???
override def parameterBase: OaiParameterBase = parameter
}
case class ListRecordsReq(parameter: CheckedListRecordsParameter) extends BaseUserRequest {