diff --git a/build.sbt b/build.sbt index dd7b6b681..add70f9ea 100644 --- a/build.sbt +++ b/build.sbt @@ -17,11 +17,18 @@ ThisBuild / semanticdbVersion := "4.13.9" // Fix dependency conflicts ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always +ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-java8-compat" % VersionScheme.Always lazy val liftVersion = "3.5.0" lazy val akkaVersion = "2.5.32" lazy val jettyVersion = "9.4.50.v20221201" lazy val avroVersion = "1.8.2" +lazy val pekkoVersion = "1.4.0" +lazy val pekkoHttpVersion = "1.3.0" +lazy val http4sVersion = "0.23.30" +lazy val catsEffectVersion = "3.5.7" +lazy val ip4sVersion = "3.7.0" +lazy val jakartaMailVersion = "2.0.1" lazy val commonSettings = Seq( resolvers ++= Seq( @@ -42,8 +49,8 @@ lazy val obpCommons = (project in file("obp-commons")) "net.liftweb" %% "lift-util" % liftVersion, "net.liftweb" %% "lift-mapper" % liftVersion, "org.scala-lang" % "scala-reflect" % "2.12.20", - "org.scalatest" %% "scalatest" % "3.2.15" % Test, - "org.scalactic" %% "scalactic" % "3.2.15", + "org.scalatest" %% "scalatest" % "3.0.9" % Test, + "org.scalactic" %% "scalactic" % "3.0.9", "net.liftweb" %% "lift-json" % liftVersion, "com.alibaba" % "transmittable-thread-local" % "2.11.5", "org.apache.commons" % "commons-lang3" % "3.12.0", @@ -95,6 +102,20 @@ lazy val obpApi = (project in file("obp-api")) "com.typesafe.akka" %% "akka-remote" % akkaVersion, "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, "com.typesafe.akka" %% "akka-http-core" % "10.1.6", + + // Pekko (ActorSystem + Pekko HTTP used by OBP runtime components) + "org.apache.pekko" %% "pekko-actor" % pekkoVersion, + "org.apache.pekko" %% "pekko-remote" % pekkoVersion, + "org.apache.pekko" %% "pekko-slf4j" % pekkoVersion, + "org.apache.pekko" %% "pekko-stream" % pekkoVersion, + "org.apache.pekko" %% "pekko-http" % pekkoHttpVersion, + + // http4s (v7.0.0 experimental stack) + "org.typelevel" %% "cats-effect" % catsEffectVersion, + "com.comcast" %% "ip4s-core" % ip4sVersion, + "org.http4s" %% "http4s-core" % http4sVersion, + "org.http4s" %% "http4s-dsl" % http4sVersion, + "org.http4s" %% "http4s-ember-server" % http4sVersion, // Avro "com.sksamuel.avro4s" %% "avro4s-core" % avroVersion, @@ -164,6 +185,9 @@ lazy val obpApi = (project in file("obp-api")) // RabbitMQ "com.rabbitmq" % "amqp-client" % "5.22.0", "net.liftmodules" %% "amqp_3.1" % "1.5.0", + + // Blockchain (Ethereum raw transaction decoding) + "org.web3j" % "core" % "4.14.0", // Elasticsearch "org.elasticsearch" % "elasticsearch" % "8.14.0", @@ -175,6 +199,7 @@ lazy val obpApi = (project in file("obp-api")) // Utilities "cglib" % "cglib" % "3.3.0", "com.sun.activation" % "jakarta.activation" % "1.2.2", + "com.sun.mail" % "jakarta.mail" % jakartaMailVersion, "com.nulab-inc" % "zxcvbn" % "1.9.0", // Testing - temporarily disabled due to version incompatibility @@ -192,7 +217,7 @@ lazy val obpApi = (project in file("obp-api")) // Test dependencies "junit" % "junit" % "4.13.2" % Test, - "org.scalatest" %% "scalatest" % "3.2.15" % Test, + "org.scalatest" %% "scalatest" % "3.0.9" % Test, "org.seleniumhq.selenium" % "htmlunit-driver" % "2.36.0" % Test, "org.testcontainers" % "rabbitmq" % "1.20.3" % Test ) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala index 1a5d8bebc..bec0f2ade 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala @@ -149,6 +149,7 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md object ResourceDocs600 extends OBPRestHelper with ResourceDocsAPIMethods with MdcLoggable { val version: ApiVersion = ApiVersion.v6_0_0 val versionStatus = ApiVersionStatus.BLEEDING_EDGE.toString + override def includeTechnologyInResponse: Boolean = true val routes: Seq[OBPEndpoint] = List( ImplementationsResourceDocs.getResourceDocsObpV400, ImplementationsResourceDocs.getResourceDocsSwagger, @@ -206,7 +207,7 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -247,4 +248,4 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md } } -} \ No newline at end of file +} diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index b355e782e..648800604 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -65,6 +65,8 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth // We add previous APIMethods so we have access to the Resource Docs self: OBPRestHelper => + def includeTechnologyInResponse: Boolean = false + val ImplementationsResourceDocs = new Object() { val localResourceDocs = ArrayBuffer[ResourceDoc]() @@ -346,7 +348,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth // Filter val rdFiltered = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, resourceDocTags, partialFunctionNames) // Format the data as json - JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher, locale) + JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) } } @@ -500,7 +502,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth NewStyle.function.tryons(s"$UnknownError Can not prepare OBP resource docs.", 500, callContext) { val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson)) resourceDocsJsonJValue.map(successJsonResponse(_)) } @@ -709,7 +711,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -903,7 +905,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -1257,4 +1259,3 @@ so the caller must specify any required filtering by catalog explicitly. } - diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 6e22b45cc..96fc6cee8 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -1796,7 +1796,8 @@ object SwaggerDefinitionsJSON { lazy val implementedByJson = ImplementedByJson( version = "1_4_0", - function = "getBranches" + function = "getBranches", + technology = None ) // Used to describe the OBP API calls for documentation and API discovery purposes lazy val canCreateCustomerSwagger = CanCreateCustomer() diff --git a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index db6f7ee9f..f47ddbf0b 100644 --- a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -328,7 +328,8 @@ object JSONFactory1_4_0 extends MdcLoggable{ // Used to describe where an API call is implemented case class ImplementedByJson ( version : String, // Short hand for the version e.g. "1_4_0" means Implementations1_4_0 - function : String // The val / partial function that implements the call e.g. "getBranches" + function : String, // The val / partial function that implements the call e.g. "getBranches" + technology: Option[String] = None ) case class ResourceDocMeta( @@ -525,11 +526,12 @@ object JSONFactory1_4_0 extends MdcLoggable{ locale: Option[String],// this will be in the cacheKey resourceDocUpdatedTags: ResourceDoc, isVersion4OrHigher:Boolean,// this will be in the cacheKey + includeTechnology: Boolean, // this will be in the cacheKey urlParametersI18n:String , jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String ): ResourceDocJson = { - val cacheKey = LOCALISED_RESOURCE_DOC_PREFIX + s"operationId:${operationId}-locale:$locale- isVersion4OrHigher:$isVersion4OrHigher".intern() + val cacheKey = LOCALISED_RESOURCE_DOC_PREFIX + s"operationId:${operationId}-locale:$locale- isVersion4OrHigher:$isVersion4OrHigher- includeTechnology:$includeTechnology".intern() Caching.memoizeSyncWithImMemory(Some(cacheKey))(CREATE_LOCALISED_RESOURCE_DOC_JSON_TTL.seconds) { val fieldsDescription = if (resourceDocUpdatedTags.tags.toString.contains("Dynamic-Entity") @@ -564,6 +566,13 @@ object JSONFactory1_4_0 extends MdcLoggable{ val summary = resourceDocUpdatedTags.summary.replaceFirst("""\.(\s*)$""", "$1") // remove the ending dot in summary val translatedSummary = I18NUtil.ResourceDocTranslation.translate(I18NResourceDocField.SUMMARY, resourceDocUpdatedTags.operationId, locale, summary) + val technology = + if (includeTechnology) { + Some(if (resourceDocUpdatedTags.http4sPartialFunction.isDefined) "http4s" else "lift") + } else { + None + } + val resourceDoc = ResourceDocJson( operation_id = resourceDocUpdatedTags.operationId, request_verb = resourceDocUpdatedTags.requestVerb, @@ -575,7 +584,11 @@ object JSONFactory1_4_0 extends MdcLoggable{ example_request_body = resourceDocUpdatedTags.exampleRequestBody, success_response_body = resourceDocUpdatedTags.successResponseBody, error_response_bodies = resourceDocUpdatedTags.errorResponseBodies, - implemented_by = ImplementedByJson(resourceDocUpdatedTags.implementedInApiVersion.fullyQualifiedVersion, resourceDocUpdatedTags.partialFunctionName), // was resourceDocUpdatedTags.implementedInApiVersion.noV + implemented_by = ImplementedByJson( + version = resourceDocUpdatedTags.implementedInApiVersion.fullyQualifiedVersion, + function = resourceDocUpdatedTags.partialFunctionName, + technology = technology + ), // was resourceDocUpdatedTags.implementedInApiVersion.noV tags = resourceDocUpdatedTags.tags.map(i => i.tag), typed_request_body = createTypedBody(resourceDocUpdatedTags.exampleRequestBody), typed_success_response_body = createTypedBody(resourceDocUpdatedTags.successResponseBody), @@ -592,7 +605,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ }} - def createLocalisedResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean, locale: Option[String], urlParametersI18n:String ,jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String) : ResourceDocJson = { + def createLocalisedResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean, locale: Option[String], includeTechnology: Boolean, urlParametersI18n:String ,jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String) : ResourceDocJson = { // We MUST recompute all resource doc values due to translation via Web UI props --> now need to wait $CREATE_LOCALISED_RESOURCE_DOC_JSON_TTL seconds val userDefinedEndpointTags = getAllEndpointTagsBox(rd.operationId).map(endpointTag =>ResourceDocTag(endpointTag.tagName)) val resourceDocWithUserDefinedEndpointTags: ResourceDoc = rd.copy(tags = userDefinedEndpointTags++ rd.tags) @@ -602,6 +615,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ locale: Option[String], resourceDocWithUserDefinedEndpointTags, isVersion4OrHigher: Boolean, + includeTechnology: Boolean, urlParametersI18n: String, jsonRequestBodyFieldsI18n: String, jsonResponseBodyFieldsI18n: String @@ -609,7 +623,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ } - def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean, locale: Option[String]) : ResourceDocsJson = { + def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean, locale: Option[String], includeTechnology: Boolean = false) : ResourceDocsJson = { val urlParametersI18n = I18NUtil.ResourceDocTranslation.translate( I18NResourceDocField.URL_PARAMETERS, "resourceDocUrlParametersString_i180n", @@ -632,11 +646,11 @@ object JSONFactory1_4_0 extends MdcLoggable{ if(isVersion4OrHigher){ ResourceDocsJson( - resourceDocList.map(createLocalisedResourceDocJson(_,isVersion4OrHigher, locale, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields)), + resourceDocList.map(createLocalisedResourceDocJson(_,isVersion4OrHigher, locale, includeTechnology, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields)), meta=Some(ResourceDocMeta(new Date(), resourceDocList.length)) ) } else { - ResourceDocsJson(resourceDocList.map(createLocalisedResourceDocJson(_,false, locale, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields))) + ResourceDocsJson(resourceDocList.map(createLocalisedResourceDocJson(_,false, locale, includeTechnology, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields))) } } diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 229c61027..70f9d8884 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -219,7 +219,7 @@ object Http4s700 { requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString)) resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil) filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions) - } yield JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) + } yield JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true) } } diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala new file mode 100644 index 000000000..5b753dd2e --- /dev/null +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala @@ -0,0 +1,46 @@ +package code.api.ResourceDocs1_4_0 + +import code.setup.{PropsReset, ServerSetup} +import net.liftweb.json.JsonAST.{JArray, JNothing, JNull, JString} + +class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { + + feature("ResourceDocs implemented_by.technology") { + + scenario("v6.0.0 resource-docs should include implemented_by.technology") { + setPropsValues("resource_docs_requires_role" -> "false") + + val request = (baseRequest / "obp" / "v6.0.0" / "resource-docs" / "v6.0.0" / "obp").GET + val response = makeGetRequest(request) + + response.code should equal(200) + (response.body \ "resource_docs") match { + case JArray(docs) => + val technology = docs.head \ "implemented_by" \ "technology" + technology should equal(JString("lift")) + case _ => + fail("Expected resource_docs field to be an array") + } + } + + scenario("v5.0.0 resource-docs should not include implemented_by.technology") { + setPropsValues("resource_docs_requires_role" -> "false") + + val request = (baseRequest / "obp" / "v5.0.0" / "resource-docs" / "v5.0.0" / "obp").GET + val response = makeGetRequest(request) + + response.code should equal(200) + (response.body \ "resource_docs") match { + case JArray(docs) => + val technology = docs.head \ "implemented_by" \ "technology" + technology match { + case JNothing | JNull => succeed + case _ => fail("Expected implemented_by.technology to be absent for v5.0.0 resource-docs") + } + case _ => + fail("Expected resource_docs field to be an array") + } + } + } +} + diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala index 7b7bce74f..19534d7dd 100644 --- a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala @@ -68,7 +68,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with def stringToNodeSeq(html : String) : NodeSeq = { val newHtmlString =scala.xml.XML.loadString("