Merge pull request #1540 from oldbig/develop

feature/dynamic_endpoints
This commit is contained in:
Simon Redfern 2020-03-30 11:45:32 +02:00 committed by GitHub
commit 0f4a2ac40f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1579 additions and 94 deletions

View File

@ -766,4 +766,7 @@ stored_procedure_connector.poolFactoryName=commons-dbcp2
# -----------------------------------------------------------------------
# Set whether DynamicEntity display name starts with underscore, default is true
dynamic_entities_have_prefix=true
dynamic_entities_have_prefix=true
# Url prefix of dynamic endpoints, default is dynamic. e.g if set to foobar, one url can be /obp/v4.0.0/foobar/Address
dynamic_endpoints_url_prefix=dynamic

View File

@ -31,6 +31,7 @@ import java.util.{Locale, TimeZone}
import code.CustomerDependants.MappedCustomerDependant
import code.DynamicData.DynamicData
import code.DynamicEndpoint.DynamicEndpoint
import code.accountapplication.MappedAccountApplication
import code.accountattribute.MappedAccountAttribute
import code.accountholders.MapperAccountHolders
@ -712,6 +713,7 @@ object ToSchemify {
Authorisation,
DynamicEntity,
DynamicData,
DynamicEndpoint,
AccountIdMapping,
DirectDebit,
StandingOrder

View File

@ -2019,34 +2019,8 @@ object SwaggerDefinitionsJSON {
branch_id = ExampleValue.branchIdExample.value,
name_suffix = ExampleValue.nameSuffixExample.value
)
val customersJsonV300 = code.api.v3_0_0.CustomerJSONs(List(customerJsonV300))
val customerWithAttributesJsonV300 = CustomerWithAttributesJsonV300(
bank_id = bankIdExample.value,
customer_id = ExampleValue.customerIdExample.value,
customer_number = ExampleValue.customerNumberExample.value,
legal_name = ExampleValue.legalNameExample.value,
mobile_phone_number = ExampleValue.mobileNumberExample.value,
email = ExampleValue.emailExample.value,
face_image = customerFaceImageJson,
date_of_birth = "19900101",
relationship_status = ExampleValue.relationshipStatusExample.value,
dependants = ExampleValue.dependentsExample.value.toInt,
dob_of_dependants = List("19900101"),
credit_rating = Option(customerCreditRatingJSON),
credit_limit = Option(amountOfMoneyJsonV121),
highest_education_attained = ExampleValue.highestEducationAttainedExample.value,
employment_status = ExampleValue.employmentStatusExample.value,
kyc_status = ExampleValue.kycStatusExample.value.toBoolean,
last_ok_date = DateWithDayExampleObject,
title = ExampleValue.titleExample.value,
branch_id = ExampleValue.branchIdExample.value,
name_suffix = ExampleValue.nameSuffixExample.value,
customer_attributes = List(customerAttributeResponseJson)
)
val customersWithAttributesJsonV300 = CustomersWithAttributesJsonV300(List(customerWithAttributesJsonV300))
val customersJsonV300 = code.api.v3_0_0.CustomerJSONs(List(customerJsonV300))
val postCustomerJsonV310 =
PostCustomerJsonV310(
@ -2123,6 +2097,32 @@ object SwaggerDefinitionsJSON {
customer_attributes = List(customerAttributeResponseJson)
)
val customerWithAttributesJsonV300 = CustomerWithAttributesJsonV300(
bank_id = bankIdExample.value,
customer_id = ExampleValue.customerIdExample.value,
customer_number = ExampleValue.customerNumberExample.value,
legal_name = ExampleValue.legalNameExample.value,
mobile_phone_number = ExampleValue.mobileNumberExample.value,
email = ExampleValue.emailExample.value,
face_image = customerFaceImageJson,
date_of_birth = "19900101",
relationship_status = ExampleValue.relationshipStatusExample.value,
dependants = ExampleValue.dependentsExample.value.toInt,
dob_of_dependants = List("19900101"),
credit_rating = Option(customerCreditRatingJSON),
credit_limit = Option(amountOfMoneyJsonV121),
highest_education_attained = ExampleValue.highestEducationAttainedExample.value,
employment_status = ExampleValue.employmentStatusExample.value,
kyc_status = ExampleValue.kycStatusExample.value.toBoolean,
last_ok_date = DateWithDayExampleObject,
title = ExampleValue.titleExample.value,
branch_id = ExampleValue.branchIdExample.value,
name_suffix = ExampleValue.nameSuffixExample.value,
customer_attributes = List(customerAttributeResponseJson)
)
val customersWithAttributesJsonV300 = CustomersWithAttributesJsonV300(List(customerWithAttributesJsonV300))
val putUpdateCustomerDataJsonV310 = PutUpdateCustomerDataJsonV310(
face_image = customerFaceImageJson,
relationship_status = ExampleValue.relationshipStatusExample.value,
@ -3631,8 +3631,7 @@ object SwaggerDefinitionsJSON {
product_code = accountTypeExample.value,
balance = amountOfMoneyJsonV121,
branch_id = branchIdExample.value,
account_routing = accountRoutingJsonV121,
account_attributes= List(accountAttributeResponseJson)
account_routing = accountRoutingJsonV121
)
val postAccountAccessJsonV400 = PostAccountAccessJsonV400(userIdExample.value, PostViewJsonV400(ExampleValue.viewIdExample.value, true))

View File

@ -81,7 +81,7 @@ import scala.collection.JavaConverters._
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.util.{ApiVersion, ReflectUtils, ScannedApiVersion}
import com.openbankproject.commons.util.{ApiVersion, JsonAble, ReflectUtils, ScannedApiVersion}
import com.openbankproject.commons.util.Functions.Implicits._
import org.apache.commons.lang3.StringUtils

View File

@ -406,6 +406,21 @@ object ApiRole {
case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity()
case class CanGetDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetDynamicEndpoint = CanGetDynamicEndpoint()
case class CanGetDynamicEndpoints(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetDynamicEndpoints = CanGetDynamicEndpoints()
case class CanCreateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateDynamicEndpoint = CanCreateDynamicEndpoint()
case class CanUpdateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateDynamicEndpoint = CanUpdateDynamicEndpoint()
case class CanDeleteDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicEndpoint = CanDeleteDynamicEndpoint()
case class CanCreateResetPasswordUrl(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateResetPasswordUrl = CanCreateResetPasswordUrl()

View File

@ -68,6 +68,7 @@ object ApiTag {
val apiTagMethodRouting = ResourceDocTag("Method-Routing")
val apiTagWebUiProps = ResourceDocTag("WebUi-Props")
val apiTagDynamicEntity= ResourceDocTag("Dynamic-Entity")
val apiTagDynamicEndpoint= ResourceDocTag("Dynamic-Endpoint")
// To mark the Berlin Group APIs suggested order of implementation
val apiTagBerlinGroupM = ResourceDocTag("Berlin-Group-M")

View File

@ -41,7 +41,7 @@ object ErrorMessages {
val WebUiPropsNotFound = "OBP-08002: WebUi props not found. Please specify a valid value for WEB_UI_PROPS_ID."
// DynamicEntity Exceptions (OBP-09XXX)
val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id."
val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for DYNAMIC_ENTITY_ID."
val DynamicEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
val DynamicEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing."
@ -49,6 +49,9 @@ object ErrorMessages {
val DynamicEntityOperationNotAllowed = "OBP-09006: Operation is not allowed, because Current DynamicEntity have upload data, must to delete all the data before this operation."
val DynamicEntityInstanceValidateFail = "OBP-09007: DynamicEntity data validation failure."
val DynamicEndpointExists = "OBP-09008: DynamicEndpoint already exists."
val DynamicEndpointNotFoundByDynamicEndpointId = "OBP-09009: DynamicEndpoint not found. Please specify a valid value for DYNAMIC_ENDPOINT_ID."
// General messages (OBP-10XXX)
val InvalidJsonFormat = "OBP-10001: Incorrect json format."

View File

@ -1,9 +1,12 @@
package code.api.util
import net.liftweb.json.JsonDSL._
import code.api.util.Glossary.{glossaryItems, makeGlossaryItem}
import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample}
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
import net.liftweb.json
import net.liftweb.json.JObject
case class ConnectorField(value: String, description: String) {
@ -369,6 +372,251 @@ object ExampleValue {
)
lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id"))
private val dynamicEndpointSwagger =
"""{
| "swagger": "2.0",
| "info": {
| "version": "0.0.1",
| "title": "Example Title",
| "description": "Example Description",
| "contact": {
| "name": "Example Company",
| "email": " simon@example.com",
| "url": "https://www.tesobe.com/"
| }
| },
| "host": "localhost:8080",
| "basePath": "/user",
| "schemes": [
| "http"
| ],
| "consumes": [
| "application/json"
| ],
| "produces": [
| "application/json"
| ],
| "paths": {
| "/save": {
| "post": {
| "parameters": [
| {
| "name": "body",
| "in": "body",
| "required": true,
| "schema": {
| "$ref": "#/definitions/user"
| }
| }
| ],
| "responses": {
| "201": {
| "description": "create user successful and return created user object",
| "schema": {
| "$ref": "#/definitions/user"
| }
| },
| "500": {
| "description": "unexpected error",
| "schema": {
| "$ref": "#/responses/unexpectedError"
| }
| }
| }
| }
| },
| "/getById/{userId}": {
| "get": {
| "description": "get reuested user by user ID",
| "parameters": [
| {
| "$ref": "#/parameters/userId"
| }
| ],
| "consumes": [],
| "responses": {
| "200": {
| "description": "the successful get requested user by user ID",
| "schema": {
| "$ref": "#/definitions/user"
| }
| },
| "400": {
| "description": "bad request",
| "schema": {
| "$ref": "#/responses/invalidRequest"
| }
| },
| "404": {
| "description": "user not found",
| "schema": {
| "$ref": "#/definitions/APIError"
| }
| },
| "500": {
| "description": "unexpected error",
| "schema": {
| "$ref": "#/responses/unexpectedError"
| }
| }
| }
| }
| },
| "/listUsers": {
| "get": {
| "description": "get list of users",
| "consumes": [],
| "responses": {
| "200": {
| "description": "get all users",
| "schema": {
| "$ref": "#/definitions/users"
| }
| },
| "404": {
| "description": "user not found",
| "schema": {
| "$ref": "#/definitions/APIError"
| }
| }
| }
| }
| },
| "/updateUser": {
| "put": {
| "parameters": [
| {
| "name": "body",
| "in": "body",
| "required": true,
| "schema": {
| "$ref": "#/definitions/user"
| }
| }
| ],
| "responses": {
| "200": {
| "description": "create user successful and return created user object",
| "schema": {
| "$ref": "#/definitions/user"
| }
| },
| "500": {
| "description": "unexpected error",
| "schema": {
| "$ref": "#/responses/unexpectedError"
| }
| }
| }
| }
| },
| "/delete/{userId}": {
| "delete": {
| "description": "delete user by user ID",
| "parameters": [
| {
| "$ref": "#/parameters/userId"
| }
| ],
| "consumes": [],
| "responses": {
| "204": {
| "description": "the successful delete user by user ID"
| },
| "400": {
| "description": "bad request",
| "schema": {
| "$ref": "#/responses/invalidRequest"
| }
| },
| "500": {
| "description": "unexpected error",
| "schema": {
| "$ref": "#/responses/unexpectedError"
| }
| }
| }
| }
| }
| },
| "definitions": {
| "user": {
| "type": "object",
| "properties": {
| "id": {
| "type": "integer",
| "description": "user ID"
| },
| "first_name": {
| "type": "string"
| },
| "last_name": {
| "type": "string"
| },
| "age": {
| "type": "integer"
| },
| "career": {
| "type": "string"
| }
| },
| "required": [
| "first_name",
| "last_name",
| "age"
| ]
| },
| "users": {
| "description": "array of users",
| "type": "array",
| "items": {
| "$ref": "#/definitions/user"
| }
| },
| "APIError": {
| "description": "content any error from API",
| "type": "object",
| "properties": {
| "errorCode": {
| "description": "content error code relate to API",
| "type": "string"
| },
| "errorMessage": {
| "description": "content user-friendly error message",
| "type": "string"
| }
| }
| }
| },
| "responses": {
| "unexpectedError": {
| "description": "unexpected error",
| "schema": {
| "$ref": "#/definitions/APIError"
| }
| },
| "invalidRequest": {
| "description": "invalid request",
| "schema": {
| "$ref": "#/definitions/APIError"
| }
| }
| },
| "parameters": {
| "userId": {
| "name": "userId",
| "in": "path",
| "required": true,
| "type": "string",
| "description": "user ID"
| }
| }
|}
|""".stripMargin
lazy val dynamicEndpointRequestBodyExample = json.parse(dynamicEndpointSwagger).asInstanceOf[JObject]
lazy val dynamicEndpointResponseBodyExample = ("dynamic_endpoint_id", "dynamic-endpoint-id") ~ ("swagger_string", dynamicEndpointRequestBodyExample)
}

View File

@ -3,7 +3,9 @@ package code.api.util
import java.util.Date
import java.util.UUID.randomUUID
import akka.http.scaladsl.model.HttpMethod
import code.DynamicData.DynamicDataProvider
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
import code.api.APIFailureNewStyle
import code.api.cache.Caching
import code.api.util.APIUtil.{OBPReturnType, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
@ -13,8 +15,9 @@ import code.api.v1_4_0.OBPAPI1_4_0.Implementations1_4_0
import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0
import code.api.v2_1_0.OBPAPI2_1_0.Implementations2_1_0
import code.api.v2_2_0.OBPAPI2_2_0.Implementations2_2_0
import code.api.v4_0_0.DynamicEntityInfo
import code.api.v4_0_0.{DynamicEndpointHelper, DynamicEntityInfo}
import code.bankconnectors.Connector
import code.bankconnectors.rest.RestConnector_vMar2019
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consumer.Consumers
import code.directdebit.DirectDebitTrait
@ -1919,6 +1922,10 @@ object NewStyle {
}
}
}
def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
Connector.connector.vend.dynamicEndpointProcess(url, jValue, method, params, pathParams, callContext)
}
def createDirectDebit(bankId : String,
@ -1996,6 +2003,54 @@ object NewStyle {
getConnectorByName(connectorName).flatMap(_.implementedMethods.get(methodName))
}
def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
Connector.connector.vend.createDynamicEndpoint(
swaggerString,
callContext
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId)
val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, DynamicEndpointNotFoundByDynamicEndpointId, 404)
Future{
(dynamicEndpoint, callContext)
}
}
def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = {
Connector.connector.vend.getDynamicEndpoints(
callContext
)
}
/**
* delete one DynamicEndpoint and corresponding entitlement and dynamic entitlement
* @param dynamicEndpointId
* @param callContext
* @return
*/
def deleteDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = {
val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(dynamicEndpointId, callContext)
for {
(entity, _) <- dynamicEndpoint
deleteSuccess = DynamicEndpointProvider.connectorMethodProvider.vend.delete(dynamicEndpointId)
deleteEndpointResult: Box[Boolean] = if(deleteSuccess) {
val roles = DynamicEndpointHelper.getRoles(dynamicEndpointId).map(_.toString())
DynamicEndpointHelper.removeEndpoint(dynamicEndpointId)
val rolesDeleteResult: Box[Boolean] = Entitlement.entitlement.vend.deleteEntitlements(roles)
Box !! (rolesDeleteResult == Full(true))
} else {
Box !! false
}
} yield {
deleteEndpointResult
}
}
def deleteCustomerAttribute(customerAttributeId : String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
Connector.connector.vend.deleteCustomerAttribute(customerAttributeId, callContext) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)

View File

@ -2227,6 +2227,7 @@ trait APIMethods310 {
emptyObjectJson,
List(
UserHasMissingRoles,
BankNotFound,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),

View File

@ -664,8 +664,7 @@ case class CreateAccountRequestJsonV310(
product_code : String,
balance : AmountOfMoneyJsonV121,
branch_id : String,
account_routing: AccountRoutingJsonV121,
account_attributes: List[AccountAttributeResponseJson]
account_routing: AccountRoutingJsonV121
)
case class CreateAccountResponseJsonV310(

View File

@ -3,6 +3,7 @@ package code.api.v4_0_0
import java.util.Date
import code.DynamicData.DynamicData
import code.DynamicEndpoint.{DynamicEndpointCommons, DynamicEndpointSwagger}
import code.accountattribute.AccountAttributeX
import code.api.ChargePolicy
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
@ -10,7 +11,7 @@ import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ApiRole._
import code.api.util.ApiTag._
import code.api.util.ErrorMessages._
import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample}
import code.api.util.ExampleValue.{dynamicEndpointRequestBodyExample, dynamicEndpointResponseBodyExample, dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample}
import code.api.util.NewStyle.HttpCode
import code.api.util.newstyle.AttributeDefinition._
import code.api.util.newstyle.Consumer._
@ -23,6 +24,7 @@ import code.api.v2_1_0._
import code.api.v2_2_0.{BankJSONV220, JSONFactory220}
import code.api.v3_0_0.JSONFactory300
import code.api.v3_1_0.{CreateAccountRequestJsonV310, CustomerWithAttributesJsonV310, JSONFactory310, ListResult}
import code.api.v4_0_0.DynamicEndpointHelper.DynamicReq
import code.api.v4_0_0.JSONFactory400.{createBankAccountJSON, createNewCoreBankAccountJson}
import code.bankconnectors.Connector
import code.dynamicEntity.{DynamicEntityCommons, ReferenceType}
@ -50,6 +52,7 @@ import net.liftweb.http.rest.RestHelper
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.Serialization.write
import net.liftweb.json._
import net.liftweb.json.JsonDSL._
import net.liftweb.mapper.By
import net.liftweb.util.Helpers.now
import net.liftweb.util.{Helpers, StringHelpers}
@ -60,6 +63,7 @@ import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import code.model._
import org.apache.commons.lang3.StringUtils
import net.liftweb.json.compactRender
trait APIMethods400 {
self: RestHelper =>
@ -131,11 +135,11 @@ trait APIMethods400 {
| INITIATED => COMPLETED
|In case n persons needs to answer security challenge we have next flow of state of an `transaction request`:
| INITIATED => NEXT_CHALLENGE_PENDING => ... => NEXT_CHALLENGE_PENDING => COMPLETED
|
|
|The security challenge is bound to a user i.e. in case of right answer and the user is different than expected one the challenge will fail.
|
|Rule for calculating number of security challenges:
|If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges
|If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges
|(one for every user that has a View where permission "can_add_transaction_request_to_any_account"=true)
|In case REQUIRED_CHALLENGE_ANSWERS is not defined as an account attribute default value is 1.
|
@ -363,7 +367,7 @@ trait APIMethods400 {
),
Catalogs(Core, PSD2, OBWG),
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagNewStyle))
// FREE_FORM.
resourceDocs += ResourceDoc(
createTransactionRequestFreeForm,
@ -424,7 +428,7 @@ trait APIMethods400 {
account = BankIdAccountId(bankId, accountId)
_ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, cc.callContext)
_ <- Helper.booleanToFuture(InsufficientAuthorisationToCreateTransactionRequest) {
u.hasOwnerViewAccess(BankIdAccountId(bankId, accountId)) ||
hasEntitlement(bankId.value, u.userId, ApiRole.canCreateAnyTransactionRequest)
@ -467,7 +471,7 @@ trait APIMethods400 {
transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, cc.callContext) {
json.extract[TransactionRequestBodyRefundJsonV400]
}
transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id)
toBankId = BankId(transactionRequestBodyRefundJson.to.bank_id)
toAccountId = AccountId(transactionRequestBodyRefundJson.to.account_id)
@ -476,28 +480,28 @@ trait APIMethods400 {
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints))
}
_ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})") {
(transaction.amount).abs >= transactionAmountNumber
}
//TODO, we need additional field to guarantee the transaction is refunded...
//TODO, we need additional field to guarantee the transaction is refunded...
// _ <- Helper.booleanToFuture(s"${RefundedTransaction}") {
// !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id(")))
// }
//we add the extro info (counterparty name + transaction_id) for this special Refund endpoint.
newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}"
//This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money.
//This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money.
refundToAccount = fromAccount
//This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money.
//This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money.
refundFromAccount = toAccount
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u,
viewId,
refundFromAccount,
refundToAccount,
transactionRequestType,
transactionRequestType,
transactionRequestBodyRefundJson.copy(description = newDescription),
transDetailsSerialized,
sharedChargePolicy.toString,
@ -782,7 +786,7 @@ trait APIMethods400 {
.findAll(By(MappedExpectedChallengeAnswer.mTransactionRequestId, transReqId.value))
.count(_.successful == true) match {
case number if number >= quorum => true
case _ =>
case _ =>
MappedTransactionRequestProvider.saveTransactionRequestStatusImpl(transReqId, TransactionRequestStatus.NEXT_CHALLENGE_PENDING.toString)
false
}
@ -813,9 +817,9 @@ trait APIMethods400 {
implementedInApiVersion,
nameOf(getDynamicEntities),
"GET",
"/management/dynamic_entities",
"Get DynamicEntities",
s"""Get the all DynamicEntities.""",
"/management/dynamic-entities",
"Get Dynamic Entities",
s"""Get the all Dynamic Entities.""",
emptyObjectJson,
ListResult(
"dynamic_entities",
@ -833,7 +837,7 @@ trait APIMethods400 {
lazy val getDynamicEntities: OBPEndpoint = {
case "management" :: "dynamic_entities" :: Nil JsonGet req => {
case "management" :: "dynamic-entities" :: Nil JsonGet req => {
cc =>
for {
dynamicEntities <- Future(NewStyle.function.getDynamicEntities())
@ -850,8 +854,8 @@ trait APIMethods400 {
implementedInApiVersion,
nameOf(createDynamicEntity),
"POST",
"/management/dynamic_entities",
"Create DynamicEntity",
"/management/dynamic-entities",
"Create Dynamic Entity",
s"""Create a DynamicEntity.
|
|
@ -883,7 +887,7 @@ trait APIMethods400 {
Some(List(canCreateDynamicEntity)))
lazy val createDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: Nil JsonPost json -> _ => {
case "management" :: "dynamic-entities" :: Nil JsonPost json -> _ => {
cc =>
val dynamicEntity = DynamicEntityCommons(json.asInstanceOf[JObject], None)
for {
@ -901,8 +905,8 @@ trait APIMethods400 {
implementedInApiVersion,
nameOf(updateDynamicEntity),
"PUT",
"/management/dynamic_entities/DYNAMIC_ENTITY_ID",
"Update DynamicEntity",
"/management/dynamic-entities/DYNAMIC_ENTITY_ID",
"Update Dynamic Entity",
s"""Update a DynamicEntity.
|
|
@ -934,7 +938,7 @@ trait APIMethods400 {
Some(List(canUpdateDynamicEntity)))
lazy val updateDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonPut json -> _ => {
case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => {
cc =>
for {
// Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity.
@ -959,8 +963,8 @@ trait APIMethods400 {
implementedInApiVersion,
nameOf(deleteDynamicEntity),
"DELETE",
"/management/dynamic_entities/DYNAMIC_ENTITY_ID",
"Delete DynamicEntity",
"/management/dynamic-entities/DYNAMIC_ENTITY_ID",
"Delete Dynamic Entity",
s"""Delete a DynamicEntity specified by DYNAMIC_ENTITY_ID.
|
|""",
@ -976,7 +980,7 @@ trait APIMethods400 {
Some(List(canDeleteDynamicEntity)))
lazy val deleteDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonDelete _ => {
case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => {
cc =>
for {
// Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity.
@ -1114,7 +1118,7 @@ trait APIMethods400 {
json.extract[PostResetPasswordUrlJsonV400]
}
} yield {
val resetLink = AuthUser.passwordResetUrl(postedData.username, postedData.email, postedData.user_id)
val resetLink = AuthUser.passwordResetUrl(postedData.username, postedData.email, postedData.user_id)
(ResetPasswordUrlJsonV400(resetLink), HttpCode.`201`(cc.callContext))
}
}
@ -2181,7 +2185,7 @@ trait APIMethods400 {
CustomerAttributeType.withName(postedData.`type`)
}
(customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, cc.callContext)
_ <- Helper.booleanToFuture(InvalidCustomerBankId.replaceAll("Bank Id.",s"Bank Id ($bankId).").replaceAll("The Customer",s"The Customer($customerId)")){customer.bankId == bankId}
_ <- Helper.booleanToFuture(InvalidCustomerBankId.replaceAll("Bank Id.",s"Bank Id ($bankId).").replaceAll("The Customer",s"The Customer($customerId)")){customer.bankId == bankId}
(accountAttribute, callContext) <- NewStyle.function.getCustomerAttributeById(
customerAttributeId,
callContext
@ -2768,7 +2772,188 @@ trait APIMethods400 {
}
}
resourceDocs += ResourceDoc(
createDynamicEndpoint,
implementedInApiVersion,
nameOf(createDynamicEndpoint),
"POST",
"/management/dynamic-endpoints",
" Create DynamicEndpoint",
s"""Create a DynamicEndpoint.
|
|Create one DynamicEndpoint,
|
|""",
dynamicEndpointRequestBodyExample,
dynamicEndpointResponseBodyExample,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
DynamicEndpointExists,
InvalidJsonFormat,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle),
Some(List(canCreateDynamicEndpoint)))
lazy val createDynamicEndpoint: OBPEndpoint = {
case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => {
cc =>
for {
(postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) {
val swaggerContent = compactRender(json)
(DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent))
}
duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._1}:${kv._2}")
errorMsg = s"""$DynamicEndpointExists Duplicated ${if(duplicatedUrl.size > 1) "endpoints" else "endpoint"}: ${duplicatedUrl.mkString("; ")}"""
_ <- Helper.booleanToFuture(errorMsg) {
duplicatedUrl.isEmpty
}
(dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext)
_ = DynamicEndpointHelper.addEndpoint(openAPI, dynamicEndpoint.dynamicEndpointId.get)
} yield {
val swaggerJson = parse(dynamicEndpoint.swaggerString)
val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson)
(responseJson, HttpCode.`201`(callContext))
}
}
}
resourceDocs += ResourceDoc(
getDynamicEndpoint,
implementedInApiVersion,
nameOf(getDynamicEndpoint),
"GET",
"/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID",
" Get DynamicEndpoint",
s"""Get a DynamicEndpoint.
|
|
|Get one DynamicEndpoint,
|
|""",
emptyObjectJson,
dynamicEndpointResponseBodyExample,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
DynamicEndpointNotFoundByDynamicEndpointId,
InvalidJsonFormat,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle),
Some(List(canGetDynamicEndpoint)))
lazy val getDynamicEndpoint: OBPEndpoint = {
case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => {
cc =>
for {
(dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext)
} yield {
val swaggerJson = parse(dynamicEndpoint.swaggerString)
val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson)
(responseJson, HttpCode.`200`(callContext))
}
}
}
resourceDocs += ResourceDoc(
getDynamicEndpoints,
implementedInApiVersion,
nameOf(getDynamicEndpoints),
"GET",
"/management/dynamic-endpoints",
" Get DynamicEndpoints",
s"""
|
|Get DynamicEndpoints.
|
|""",
emptyObjectJson,
ListResult(
"dynamic_endpoints",
List(dynamicEndpointResponseBodyExample)
),
List(
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle),
Some(List(canGetDynamicEndpoints)))
lazy val getDynamicEndpoints: OBPEndpoint = {
case "management" :: "dynamic-endpoints" :: Nil JsonGet _ => {
cc =>
for {
(dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext)
} yield {
val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=>
val swaggerJson = parse(dynamicEndpoint.swaggerString)
("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson)
}
(ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext))
}
}
}
resourceDocs += ResourceDoc(
deleteDynamicEndpoint,
implementedInApiVersion,
nameOf(deleteDynamicEndpoint),
"DELETE",
"/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID",
" Delete Dynamic Endpoint",
s"""Delete a DynamicEndpoint specified by DYNAMIC_ENDPOINT_ID.
|
|""",
emptyObjectJson,
emptyObjectJson,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
DynamicEndpointNotFoundByDynamicEndpointId,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle),
Some(List(canDeleteDynamicEndpoint)))
lazy val deleteDynamicEndpoint : OBPEndpoint = {
case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => {
cc =>
for {
deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext)
} yield {
(deleted, HttpCode.`204`(cc.callContext))
}
}
}
lazy val dynamicEndpoint: OBPEndpoint = {
case DynamicReq(url, json, method, params, pathParams, role) => { cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, role, callContext)
(box, _) <- NewStyle.function.dynamicEndpointProcess(url, json, method, params, pathParams, callContext)
} yield {
box match {
case Full(v) => (v, HttpCode.`200`(Some(cc)))
case e: Failure => (e.messageChain, HttpCode.`200`(Some(cc))) // TODO code need change
case _ => ("fail", HttpCode.`200`(Some(cc)))
}
}
}
}
resourceDocs += ResourceDoc(
createOrUpdateCustomerAttributeAttributeDefinition,

View File

@ -0,0 +1,454 @@
package code.api.v4_0_0
import java.io.File
import java.nio.charset.Charset
import java.util
import java.util.concurrent.CopyOnWriteArrayList
import java.util.regex.Pattern
import java.util.{Date, Optional}
import akka.http.scaladsl.model.HttpMethods
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
import code.api.util.APIUtil.{Catalogs, OBPEndpoint, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2}
import code.api.util.ApiTag.{ResourceDocTag, apiTagApi, apiTagNewStyle}
import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.{APIUtil, ApiRole, ApiTag, CustomJsonFormats, NewStyle}
import code.api.util.ApiRole.getOrCreateDynamicApiRole
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
import com.openbankproject.commons.util.{ApiVersion, Functions}
import io.swagger.v3.oas.models.{OpenAPI, Operation, PathItem}
import io.swagger.v3.oas.models.PathItem.HttpMethod
import akka.http.scaladsl.model.{HttpMethod => AkkaHttpMethod}
import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Content, DateSchema, DateTimeSchema, IntegerSchema, NumberSchema, ObjectSchema, Schema, StringSchema}
import io.swagger.v3.oas.models.parameters.RequestBody
import io.swagger.v3.oas.models.responses.ApiResponses
import io.swagger.v3.parser.OpenAPIV3Parser
import net.liftweb.http.Req
import net.liftweb.http.rest.RestHelper
import net.liftweb.json.JsonAST.{JArray, JField, JNothing, JObject}
import net.liftweb.json.JsonDSL._
import net.liftweb.json
import net.liftweb.json.JValue
import net.liftweb.util.StringHelpers
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.StringUtils
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.collection.JavaConverters._
import scala.compat.java8.OptionConverters._
object DynamicEndpointHelper extends RestHelper {
/**
* dynamic endpoints url prefix
*/
val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic")
private lazy val dynamicEndpointInfos: CopyOnWriteArrayList[DynamicEndpointInfo] = {
val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll()
val infos = dynamicEndpoints.map(it => swaggerToResourceDocs(it.swaggerString, it.dynamicEndpointId.get))
new CopyOnWriteArrayList(infos.asJava)
}
def getRoles(dynamicEndpointId: String): List[ApiRole] = {
val foundInfos: Option[DynamicEndpointInfo] = dynamicEndpointInfos.asScala
.find(_.id == dynamicEndpointId)
val roles = foundInfos.toList
.flatMap(_.resourceDocs)
.map(_.roles)
.collect {
case Some(role :: _) => role
}
roles
}
/**
* extract request body, no matter GET, POST, PUT or DELETE method
*/
object DynamicReq extends JsonTest with JsonBody {
private val ExpressionRegx = """\{(.+?)\}""".r
/**
* unapply Request to (request url, json, http method, request parameters, path parameters, role)
* request url is current request url
* json is request body
* http method is request http method
* request parameters is http request parameters
* path parameters: /banks/{bankId}/users/{userId} bankId and userId corresponding key to value
* role is current endpoint required entitlement
* @param r HttpRequest
* @return
*/
def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole)] = {
val partPath = r.path.partPath
if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix))
None
else {
val akkaHttpMethod = HttpMethods.getForKeyCaseInsensitive(r.requestType.method).get
val httpMethod = HttpMethod.valueOf(r.requestType.method)
// url that match original swagger endpoint.
val url = partPath.tail.mkString("/", "/", "")
val foundDynamicEndpoint: Optional[(DynamicEndpointInfo, ResourceDoc, String)] = dynamicEndpointInfos.stream()
.map[Option[(DynamicEndpointInfo, ResourceDoc, String)]](_.findDynamicEndpoint(httpMethod, url))
.filter(_.isDefined)
.findFirst()
.map(_.get)
foundDynamicEndpoint.asScala
.flatMap[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole)] { it =>
val (dynamicEndpointInfo, doc, originalUrl) = it
val pathParams: Map[String, String] = if(originalUrl == url) {
Map.empty[String, String]
} else {
val tuples: Array[(String, String)] = StringUtils.split(originalUrl, "/").zip(partPath.tail)
tuples.collect {
case (ExpressionRegx(name), value) => name->value
}.toMap
}
val Some(role::_) = doc.roles
body(r).toOption
.orElse(Some(JNothing))
.map(t => (dynamicEndpointInfo.targetUrl(url), t, akkaHttpMethod, r.params, pathParams, role))
}
}
}
}
def addEndpoint(openAPI: OpenAPI, id: String): Boolean = {
val endpointInfo = swaggerToResourceDocs(openAPI, id)
dynamicEndpointInfos.add(endpointInfo)
}
def removeEndpoint(id: String): Boolean = {
dynamicEndpointInfos.asScala.find(_.id == id) match {
case Some(v) => dynamicEndpointInfos.remove(v)
case _ => false
}
}
def findExistsEndpoints(openAPI: OpenAPI): List[(HttpMethod, String)] = {
for {
(path, pathItem) <- openAPI.getPaths.asScala.toList
(method: HttpMethod, _) <- pathItem.readOperationsMap.asScala
if dynamicEndpointInfos.stream().anyMatch(_.existsEndpoint(method, path))
} yield (method, path)
}
private def swaggerToResourceDocs(content: String, id: String): DynamicEndpointInfo = {
val openAPI: OpenAPI = parseSwaggerContent(content)
swaggerToResourceDocs(openAPI, id)
}
private def swaggerToResourceDocs(openAPI: OpenAPI, id: String): DynamicEndpointInfo = {
val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle)
val serverUrl = {
val servers = openAPI.getServers
assert(!servers.isEmpty, s"swagger host is mandatory, but current swagger host is empty, id=$id")
servers.get(0).getUrl
}
val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala
def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement
val docs: mutable.Iterable[(ResourceDoc, String)] = for {
(path, pathItem) <- paths
(method: HttpMethod, op: Operation) <- pathItem.readOperationsMap.asScala
} yield {
val implementedInApiVersion = ApiVersion.v4_0_0
val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // this function is just placeholder, not need a real value.
val partialFunctionName: String = s"$method-$path".replaceAll("\\W", "_")
val requestVerb: String = method.name()
val requestUrl: String = buildRequestUrl(path)
val summary: String = Option(pathItem.getSummary)
.filter(StringUtils.isNotBlank)
.getOrElse(buildSummary(method, op, path))
val description: String = Option(pathItem.getDescription)
.filter(StringUtils.isNotBlank)
.orElse(Option(op.getDescription))
.filter(StringUtils.isNotBlank)
.map(_.capitalize)
.getOrElse(summary) +
s"""
|
|MethodRouting settings example:
|```
|{
| "is_bank_id_exact_match":false,
| "method_name":"dynamicEndpointProcess",
| "connector_name":"rest_vMar2019",
| "bank_id_pattern":".*",
| "parameters":[
| {
| "key":"url_pattern",
| "value":"$serverUrl$path"
| },
| {
| "key":"http_method",
| "value":"$requestVerb"
| }
| {
| "key":"url",
| "value":"http://mydomain.com/xxx"
| }
| ]
|}
|```
|
|""".stripMargin
val exampleRequestBody: Product = getRequestExample(openAPI, op.getRequestBody)
val successResponseBody: Product = getResponseExample(openAPI, op.getResponses)
val errorResponseBodies: List[String] = List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
)
val catalogs: Catalogs = Catalogs(notCore, notPSD2, notOBWG)
val roles: Option[List[ApiRole]] = {
val roleName = s"Can$summary${entitlementSuffix(path)}"
.replaceFirst("Can(Create|Update|Get|Delete)", "Can$1Dynamic")
.replace(" ", "")
Some(List(
ApiRole.getOrCreateDynamicApiRole(roleName)
))
}
val connectorMethods = Some(List("dynamicEndpointProcess"))
val doc = ResourceDoc(
partialFunction,
implementedInApiVersion,
partialFunctionName,
requestVerb,
requestUrl,
summary,
description,
exampleRequestBody,
successResponseBody,
errorResponseBodies,
catalogs,
tags,
roles,
connectorMethods = connectorMethods
)
(doc, path)
}
DynamicEndpointInfo(id, docs, serverUrl)
}
private val PathParamRegx = """\{(.+?)\}""".r
private val WordBoundPattern = Pattern.compile("(?<=[a-z])(?=[A-Z])|-")
private def buildRequestUrl(path: String): String = {
val url = StringUtils.split(s"$urlPrefix/$path", "/")
url.map {
case PathParamRegx(param) => WordBoundPattern.matcher(param).replaceAll("_").toUpperCase()
case v => v
}.mkString("/", "/", "")
}
def parseSwaggerContent(content: String): OpenAPI = {
val tempSwaggerFile = File.createTempFile("temp", ".swagger")
FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8"))
val openAPI: OpenAPI = new OpenAPIV3Parser().read(tempSwaggerFile.getAbsolutePath)
// Delete temp file when program exits, only if delete fail.
if(!FileUtils.deleteQuietly(tempSwaggerFile)){
tempSwaggerFile.deleteOnExit()
}
openAPI
}
def doc: ArrayBuffer[ResourceDoc] = {
val docs = ArrayBuffer[ResourceDoc]()
dynamicEndpointInfos.forEach { info =>
info.resourceDocs.foreach { doc =>
docs += doc
}
}
docs
}
private def buildSummary(method: HttpMethod, op: Operation, path: String): String = method match {
case _ if StringUtils.isNotBlank(op.getSummary) => op.getSummary
case HttpMethod.GET | HttpMethod.DELETE =>
val opName = if(method == HttpMethod.GET) "Get" else "Delete"
op.getResponses.asScala
.find(_._1.startsWith("20"))
.flatMap(it => getRef(it._2.getContent, it._2.get$ref()) )
.map(StringUtils.substringAfterLast(_, "/"))
.map(entityName => s"$opName $entityName")
.orElse(Option(op.getDescription))
.filter(StringUtils.isNotBlank)
.orElse(Option(s"$opName $path"))
.map(_.replaceFirst("(?i)((get|delete)\\s+\\S+).*", "$1"))
.map(capitalize)
.get
case m@(HttpMethod.POST | HttpMethod.PUT) =>
val opName = if(m == HttpMethod.POST) "Create" else "Update"
getRef(op.getRequestBody.getContent, op.getRequestBody.get$ref())
.map(StringUtils.substringAfterLast(_, "/"))
.map(entityName => s"$opName $entityName")
.orElse(Option(op.getDescription))
.filter(StringUtils.isNotBlank)
.orElse(Option(s"$method $path"))
.map(capitalize)
.get
case _ => throw new RuntimeException(s"Support HTTP METHOD: GET, POST, PUT, DELETE, current method is $method")
}
private def capitalize(str: String): String =
StringUtils.split(str, " ").map(_.capitalize).mkString(" ")
private def getRequestExample(openAPI: OpenAPI, body: RequestBody): Product = {
if(body == null || body.getContent == null) {
JObject()
} else {
getExample(openAPI, getRef(body.getContent, body.get$ref()).orNull)
}
}
private def getResponseExample(openAPI: OpenAPI, apiResponses: ApiResponses): Product = {
if(apiResponses == null || apiResponses.isEmpty) {
JObject()
} else {
val ref: Option[String] = apiResponses.asScala
.find(_._1.startsWith("20"))
.flatMap(it => getRef(it._2.getContent, it._2.get$ref()))
getExample(openAPI, ref.orNull)
}
}
private def getRef(content: Content, $ref: String): Option[String] = {
if(StringUtils.isNoneBlank($ref)) {
Option($ref)
} else {
val schemaRef: Option[String] = Option(content.get("application/json"))
.flatMap(it => Option[Schema[_]](it.getSchema))
.map(_.get$ref())
.filter(StringUtils.isNoneBlank(_))
if(schemaRef.isDefined) {
Option(schemaRef.get)
} else {
val supportMediaTypes = content.values().asScala
supportMediaTypes.collectFirst {
case mediaType if mediaType.getSchema != null && StringUtils.isNotBlank(mediaType.getSchema.get$ref()) =>
mediaType.getSchema.get$ref()
}
}
}
}
private val RegexDefinitions = """(?:#/components/schemas(?:/#definitions)?/)(.+)""".r
private val RegexResponse = """#/responses/(.+)""".r
private def getExample(openAPI: OpenAPI, ref: String): Product = {
implicit val formats = CustomJsonFormats.formats
ref match {
case null => JObject()
case RegexResponse(refName) =>
val response = openAPI.getComponents.getResponses.get(refName)
val ref = getRef(response.getContent, response.get$ref())
getExample(openAPI, ref.get)
case RegexDefinitions(refName) =>
openAPI.getComponents.getSchemas.get(refName) match {
case o: ObjectSchema =>
val properties: util.Map[String, Schema[_]] = o.getProperties
val jFields: mutable.Iterable[JField] = properties.asScala.map { kv =>
val (name, value) = kv
val valueExample = if(value.getClass == classOf[Schema[_]]) getExample(openAPI, value.get$ref()) else getPropertyExample(value)
JField(name, json.Extraction.decompose(valueExample))
}
JObject(jFields.toList)
case a: ArraySchema =>
Option(a.getExample)
.map(json.Extraction.decompose(_).asInstanceOf[JObject])
.getOrElse {
val schema: Schema[_] = a.getItems
val singleItem: Any = if(schema.getClass == classOf[Schema[_]]) getExample(openAPI, schema.get$ref()) else getPropertyExample(schema)
val jItem = json.Extraction.decompose(singleItem)
jItem :: Nil
}
}
}
}
private def getPropertyExample(schema: Schema[_]) = schema match {
case b: BooleanSchema => Option(b.getExample).getOrElse(true)
case d: DateSchema => Option(d.getExample).getOrElse {
APIUtil.DateWithDayFormat.format(new Date())
}
case t: DateTimeSchema => Option(t.getExample).getOrElse {
APIUtil.DateWithSecondsFormat.format(new Date())
}
case i: IntegerSchema => Option(i.getExample).getOrElse(1)
case n: NumberSchema => Option(n.getExample).getOrElse(1.2)
case s: StringSchema => Option(s.getExample).getOrElse("string")
case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.")
}
}
/**
*
* @param id DynamicEntity id value
* @param docsToUrl ResourceDoc to url that defined in swagger content
* @param serverUrl base url that defined in swagger content
*/
case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(ResourceDoc, String)], serverUrl: String) {
val resourceDocs: mutable.Iterable[ResourceDoc] = docsToUrl.map(_._1)
private val existsUrlToMethod: mutable.Iterable[(HttpMethod, String, ResourceDoc)] =
docsToUrl
.map(it => {
val (doc, path) = it
(HttpMethod.valueOf(doc.requestVerb), path, doc)
})
def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(DynamicEndpointInfo, ResourceDoc, String)] = existsUrlToMethod.find(it => {
val (method, url, _) = it
isSameUrl(newUrl, url) && newMethod == method
}).map(it => (this, it._3, it._2))
def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined
def targetUrl(url: String): String = s"""$serverUrl$url"""
/**
* check whether two url is the same:
* isSameUrl("/abc/efg", "/abc/efg") == true
* isSameUrl("/abc/efg", "/abc/{id}") == true
* isSameUrl("/abc/{userId}", "/abc/{id}") == true
* isSameUrl("/abc/{userId}/", "/abc/{id}") == true
* isSameUrl("/def/abc/", "/abc/{id}") == false
* @param pathX
* @param pathY
* @return
*/
private def isSameUrl(pathX: String, pathY: String) = {
val splitPathX = StringUtils.split(pathX, '/')
val splitPathY = StringUtils.split(pathY, '/')
if(splitPathX.size != splitPathY.size) {
false
} else {
splitPathX.zip(splitPathY).forall {kv =>
val (partX, partY) = kv
partX == partY ||
(partX.startsWith("{") && partX.endsWith("}")) ||
(partY.startsWith("{") && partY.endsWith("}"))
}
}
}
}

View File

@ -32,7 +32,7 @@ object MockerConnector {
def definitionsMap = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName))).toMap
def doc = {
def doc: ArrayBuffer[ResourceDoc] = {
val docs: Seq[ResourceDoc] = definitionsMap.values.flatMap(createDocs).toSeq
collection.mutable.ArrayBuffer(docs:_*)
}

View File

@ -36,18 +36,16 @@ import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSON
import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140
import code.api.v2_0_0.TransactionRequestChargeJsonV200
import code.api.v2_1_0.ResourceUserJSON
import code.api.v2_2_0.ConsumerJson
import code.api.v3_0_0.JSONFactory300.createAccountRoutingsJSON
import code.api.v3_0_0.{CustomerAttributeResponseJsonV300, ViewBasicV300}
import code.api.v3_1_0.AccountAttributeResponseJson
import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson
import code.directdebit.DirectDebitTrait
import code.entitlement.Entitlement
import code.model.{Consumer, ModeratedBankAccount, ModeratedBankAccountCore}
import code.model.{Consumer, ModeratedBankAccountCore}
import code.standingorders.StandingOrderTrait
import code.transactionChallenge.MappedExpectedChallengeAnswer
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.users.Users
import com.openbankproject.commons.model._
import net.liftweb.common.{Box, Full}

View File

@ -55,7 +55,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
// Possible Endpoints from 4.0.0, exclude one endpoint use - method,exclude multiple endpoints use -- method,
// e.g getEndpoints(Implementations4_0_0) -- List(Implementations4_0_0.genericEndpoint, Implementations4_0_0.root)
val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint
val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint - Implementations4_0_0.dynamicEndpoint
lazy val excludeEndpoints =
nameOf(Implementations1_2_1.addPermissionForUserForBankAccountForMultipleViews) ::
@ -67,7 +67,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
// if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc.
def allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs,
Implementations4_0_0.resourceDocs,
MockerConnector.doc)
MockerConnector.doc, DynamicEndpointHelper.doc)
.filterNot(it => it.partialFunctionName.matches(excludeEndpoints.mkString("|")))
//TODO exclude two endpoints, after training we need add logic to exclude endpoints
@ -85,6 +85,8 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
registerRoutes(routes, allResourceDocs, apiPrefix, true)
oauthServe(apiPrefix{Implementations4_0_0.genericEndpoint}, None)
oauthServe(apiPrefix{Implementations4_0_0.dynamicEndpoint}, None)
logger.info(s"version $version has been run! There are ${routes.length} routes.")
// specified response for OPTIONS request.

View File

@ -3,6 +3,7 @@ package code.bankconnectors
import java.util.Date
import java.util.UUID.randomUUID
import code.DynamicEndpoint.DynamicEndpointT
import code.accountholders.{AccountHolders, MapperAccountHolders}
import code.api.attributedefinition.AttributeDefinition
import code.api.{APIFailure, APIFailureNewStyle}
@ -59,6 +60,7 @@ import scala.concurrent.duration._
import scala.math.{BigDecimal, BigInt}
import scala.util.Random
import scala.reflect.runtime.universe.{MethodSymbol, typeOf}
import _root_.akka.http.scaladsl.model.HttpMethod
/*
So we can switch between different sources of resources e.g.
@ -2181,6 +2183,9 @@ trait Connector extends MdcLoggable {
requestBody: Option[JObject],
entityId: Option[String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future{(Failure(setUnimplementedError), callContext)}
def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future{(Failure(setUnimplementedError), callContext)}
def createDirectDebit(bankId: String,
accountId: String,
@ -2210,4 +2215,16 @@ trait Connector extends MdcLoggable {
def deleteCustomerAttribute(customerAttributeId: String,
callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)}
def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future {
(Failure(setUnimplementedError), callContext)
}
def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future {
(Failure(setUnimplementedError), callContext)
}
def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
(List.empty[DynamicEndpointT], callContext)
}
}

View File

@ -5,6 +5,7 @@ import java.util.UUID.randomUUID
import scala.concurrent.duration._
import code.DynamicData.DynamicDataProvider
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
import code.accountapplication.AccountApplicationX
import code.accountattribute.AccountAttributeX
import code.accountholders.{AccountHolders, MapperAccountHolders}
@ -78,7 +79,6 @@ import net.liftweb.mapper.{By, _}
import net.liftweb.util.Helpers.{hours, now, time, tryo}
import net.liftweb.util.Mailer
import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To}
import org.apache.commons.lang3.StringUtils
import org.mindrot.jbcrypt.BCrypt
import scalacache.ScalaCache
import scalacache.guava.GuavaCache
@ -91,6 +91,7 @@ import scala.language.postfixOps
import scala.math.{BigDecimal, BigInt}
import scala.util.Random
import _root_.akka.http.scaladsl.model.HttpMethod
object LocalMappedConnector extends Connector with MdcLoggable {
@ -3158,6 +3159,13 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
}
/* delegate to rest connector
*/
override def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
Connector.getConnectorInstance("rest_vMar2019").dynamicEndpointProcess(url,jValue, method, params, pathParams, callContext)
}
override def createDirectDebit(bankId: String,
accountId: String,
customerId: String,
@ -4144,6 +4152,18 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
Full(res)
}
override def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.create(swaggerString), callContext)
}
override def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId), callContext)
}
override def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(), callContext)
}
override def deleteCustomerAttribute(customerAttributeId: String, callContext: Option[CallContext] ): OBPReturnType[Box[Boolean]] = {
CustomerAttributeX.customerAttributeProvider.vend.deleteCustomerAttribute(customerAttributeId) map { ( _, callContext) }

View File

@ -1,7 +1,9 @@
package code
import java.lang.reflect.Method
import java.util.regex.Pattern
import akka.http.scaladsl.model.HttpMethod
import code.api.{APIFailureNewStyle, ApiVersionHolder}
import code.api.util.{CallContext, NewStyle}
import code.methodrouting.{MethodRouting, MethodRoutingT}
@ -17,7 +19,7 @@ import scala.collection.mutable.ArrayBuffer
import scala.reflect.runtime.universe.{MethodSymbol, Type, typeOf}
import code.api.util.ErrorMessages.InvalidConnectorResponseForMissingRequiredValues
import code.api.util.APIUtil.fullBoxOrException
import com.openbankproject.commons.util.ApiVersion
import com.openbankproject.commons.util.{ApiVersion, ReflectUtils}
import com.openbankproject.commons.util.ReflectUtils._
import com.openbankproject.commons.util.Functions.Implicits._
import net.liftweb.util.ThreadGlobal
@ -97,6 +99,18 @@ package object bankconnectors extends MdcLoggable {
NewStyle.function.getMethodRoutings(Some(methodName))
.find(_.parameters.exists(it => it.key == "entityName" && it.value == entityName))
}
case _ if methodName == "dynamicEndpointProcess" => {
val Array(url: String, _, method: HttpMethod, _*) = args
NewStyle.function.getMethodRoutings(Some(methodName))
.find(routing => {
routing.parameters.exists(it => it.key == "http_method" && it.value.equalsIgnoreCase(method.value)) &&
routing.parameters.exists(it => it.key == "url")&&
routing.parameters.exists(
it => it.key == "url_pattern" &&
(it.value == url || Pattern.compile(it.value).matcher(url).matches())
)
})
}
case None => NewStyle.function.getMethodRoutings(Some(methodName), Some(false))
.find {routing =>
val bankIdPattern = routing.bankIdPattern
@ -123,7 +137,7 @@ package object bankconnectors extends MdcLoggable {
case name => Connector.getConnectorInstance(name)
}
val methodSymbol = connector.implementedMethods(methodName).alternatives match {
case m::Nil if m.isInstanceOf[MethodSymbol] => m.asMethod
case m::Nil if m.isMethod => m.asMethod
case _ =>
findMethodByArgs(connector, methodName, args:_*)
.getOrElse(sys.error(s"not found matched method, method name: ${methodName}, params: ${args.mkString(",")}"))
@ -179,7 +193,8 @@ package object bankconnectors extends MdcLoggable {
processObj match {
case None => None
case Some(value) => {
case Some(value) if ReflectUtils.isObpObject(value) => {
val argNameToValues: Map[String, Any] = getConstructorArgs(value)
//find from current object constructor args
// orElse: if current object constructor args not found value, recursive search args
@ -192,6 +207,8 @@ package object bankconnectors extends MdcLoggable {
.find(it => it.isDefined)
}
}
case _ => None
}
}

View File

@ -61,8 +61,10 @@ import code.model.dataAccess.internalMapping.MappedAccountIdMappingProvider
import code.util.{Helper, JsonUtils}
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation}
import net.liftweb.json._
import net.liftweb.json
import net.liftweb.json.{JValue, _}
import net.liftweb.json.Extraction.decompose
import org.apache.commons.lang3.StringUtils
trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable {
//this one import is for implicit convert, don't delete
@ -9324,6 +9326,81 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
val result: OBPReturnType[Box[JValue]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
result
}
override def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
val urlInMethodRouting: Option[String] = MethodRoutingHolder.methodRouting match {
case _: EmptyBox => None
case Full(routing) => routing.parameters.find(_.key == "url").map(_.value)
}
val pathVariableRex = """\{:(.+?)\}""".r
val targetUrl = urlInMethodRouting.map { urlInRouting =>
val tuples: Iterator[(String, String)] = pathVariableRex.findAllMatchIn(urlInRouting).map{ regex =>
val expression = regex.group(0)
val paramName = regex.group(1)
val paramValue =
if(paramName.startsWith("body.")) {
val path = StringUtils.substringAfter(paramName, "body.")
val value = JsonUtils.getValueByPath(jValue, path)
JsonUtils.toString(value)
} else {
pathParams.get(paramName)
.orElse(params.get(paramName).flatMap(_.headOption)).getOrElse(throw new RuntimeException(s"param $paramName not exists."))
}
expression -> paramValue
}
(urlInRouting /: tuples) {(pre, kv)=>
pre.replace(kv._1, kv._2)
}
}.getOrElse(url)
val paramNameToValue = for {
(name, values) <- params
value <- values
param = s"$name=$value"
} yield param
val paramUrl: String =
if(params.isEmpty){
targetUrl
} else if(targetUrl.contains("?")) {
targetUrl + "&" + paramNameToValue.mkString("&")
} else {
targetUrl + "?" + paramNameToValue.mkString("&")
}
val jsonToSend = if(jValue == JNothing) "" else compactRender(jValue)
val request = prepareHttpRequest(paramUrl, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(callContext)
logger.debug(s"RestConnector_vMar2019 request is : $request")
val responseFuture = makeHttpRequest(request)
val result: Future[(Box[JValue], Option[CallContext])] = responseFuture.map {
case response@HttpResponse(status, _, entity@_, _) => (status, entity)
}.flatMap {
case (status, entity) if status.isSuccess() =>
this.extractBody(entity)
.map{
case v if StringUtils.isBlank(v) => (Empty, callContext)
case v => (Full(json.parse(v)), callContext)
}
case (status, entity) => {
val future: Future[Box[Box[JValue]]] = extractBody(entity) map { msg =>
tryo {
val failure: Box[JValue] = ParamFailure(msg, APIFailureNewStyle(msg, status.intValue()))
failure
} ~> APIFailureNewStyle(msg, status.intValue())
}
future.map{
case Full(v) => (v, callContext)
case e: EmptyBox => (e, callContext)
}
}
}
result
}
//In RestConnector, we use the headers to propagate the parameters to Adapter. The parameters come from the CallContext.outboundAdapterAuthInfo.userAuthContext

View File

@ -0,0 +1,34 @@
package code.DynamicEndpoint
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object DynamicEndpointProvider extends SimpleInjector {
val connectorMethodProvider = new Inject(buildOne _) {}
def buildOne: MappedDynamicEndpointProvider.type = MappedDynamicEndpointProvider
}
trait DynamicEndpointT {
def dynamicEndpointId: Option[String]
def swaggerString: String
}
case class DynamicEndpointCommons(
dynamicEndpointId: Option[String] = None,
swaggerString: String
) extends DynamicEndpointT with JsonFieldReName
object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons]
case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Option[String] = None)
trait DynamicEndpointProvider {
def create(swaggerString: String): Box[DynamicEndpointT]
def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT]
def get(dynamicEndpointId: String): Box[DynamicEndpointT]
def getAll(): List[DynamicEndpointT]
def delete(dynamicEndpointId: String): Boolean
}

View File

@ -0,0 +1,40 @@
package code.DynamicEndpoint
import code.api.util.CustomJsonFormats
import code.util.MappedUUID
import net.liftweb.common.Box
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
object MappedDynamicEndpointProvider extends DynamicEndpointProvider with CustomJsonFormats{
override def create(swaggerString: String): Box[DynamicEndpointT] = {
tryo{DynamicEndpoint.create.SwaggerString(swaggerString).saveMe()}
}
override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = {
DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe())
}
override def get(dynamicEndpointId: String): Box[DynamicEndpointT] = DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
override def getAll(): List[DynamicEndpointT] = DynamicEndpoint.findAll()
override def delete(dynamicEndpointId: String): Boolean = DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId))
}
class DynamicEndpoint extends DynamicEndpointT with LongKeyedMapper[DynamicEndpoint] with IdPK {
override def getSingleton = DynamicEndpoint
object DynamicEndpointId extends MappedUUID(this)
object SwaggerString extends MappedText(this)
override def dynamicEndpointId: Option[String] = Option(DynamicEndpointId.get)
override def swaggerString: String = SwaggerString.get
}
object DynamicEndpoint extends DynamicEndpoint with LongKeyedMetaMapper[DynamicEndpoint] {
override def dbIndexes = UniqueIndex(DynamicEndpointId) :: super.dbIndexes
}

View File

@ -32,6 +32,7 @@ trait EntitlementProvider {
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]]
def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement]
def deleteDynamicEntityEntitlement(entityName: String) : Box[Boolean]
def deleteEntitlements(entityNames: List[String]) : Box[Boolean]
}
trait Entitlement {
@ -54,6 +55,7 @@ class RemotedataEntitlementsCaseClasses {
case class getEntitlementsByRoleFuture(roleName: String)
case class addEntitlement(bankId: String, userId: String, roleName: String)
case class deleteDynamicEntityEntitlement(entityName: String)
case class deleteEntitlements(entityNames: List[String])
}
object RemotedataEntitlementsCaseClasses extends RemotedataEntitlementsCaseClasses

View File

@ -94,8 +94,12 @@ object MappedEntitlementsProvider extends EntitlementProvider {
override def deleteDynamicEntityEntitlement(entityName: String): Box[Boolean] = {
val roleNames = DynamicEntityInfo.roleNames(entityName)
deleteEntitlements(roleNames)
}
override def deleteEntitlements(entityNames: List[String]) : Box[Boolean] = {
Box.tryo{
MappedEntitlement.bulkDelete_!!(ByList(MappedEntitlement.mRoleName, roleNames))
MappedEntitlement.bulkDelete_!!(ByList(MappedEntitlement.mRoleName, entityNames))
}
}

View File

@ -56,4 +56,8 @@ object RemotedataEntitlements extends ObpActorInit with EntitlementProvider {
(actor ? cc.deleteDynamicEntityEntitlement(entityName)).mapTo[Box[Boolean]]
)
override def deleteEntitlements(entityNames: List[String]) : Box[Boolean] = getValueFromFuture(
(actor ? cc.deleteEntitlements(entityNames)).mapTo[Box[Boolean]]
)
}

View File

@ -63,6 +63,10 @@ class RemotedataEntitlementsActor extends Actor with ObpActorHelper with MdcLogg
logger.debug(s"deleteDynamicEntityEntitlement($entityName)")
sender ! (mapper.deleteDynamicEntityEntitlement(entityName))
case cc.deleteEntitlements(entityNames) =>
logger.debug(s"deleteEntitlements($entityNames)")
sender ! (mapper.deleteEntitlements(entityNames))
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}

View File

@ -163,6 +163,7 @@ object JsonUtils {
} else {
JField(newName, jObj)
}
case _ => throw new RuntimeException(s"Not support json value type, value is: $jValue")
}
}
@ -420,7 +421,7 @@ object JsonUtils {
* @param pathExpress path, can be prefix by - or !, e.g: "-result.count" "!value.isDeleted"
* @return given nested field value
*/
private def getValueByPath(jValue: JValue, pathExpress: String): JValue = {
def getValueByPath(jValue: JValue, pathExpress: String): JValue = {
pathExpress match {
case str if str.trim == "$root" || str.trim.isEmpty => jValue // if path is "$root" or "", return whole original json
case RegexBoolean(b) => JBool(b.toBoolean)
@ -479,4 +480,14 @@ object JsonUtils {
case v => v.values.toString == expectValue
}
}
def toString(jValue: JValue) = jValue match{
case JString(s) => s
case JInt(num) => num.toString()
case JDouble(num) => num.toString()
case JBool(b) => b.toString()
case JNothing => ""
case JNull => "null"
case v => json.compactRender(v)
}
}

View File

@ -152,7 +152,7 @@ class DynamicEntityTest extends V400ServerSetup {
feature("Add a DynamicEntity v4.0.4- Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST
val request400 = (v4_0_0_Request / "management" / "dynamic-entities").POST
val response400 = makePostRequest(request400, write(rightEntity))
Then("We should get a 400")
response400.code should equal(400)
@ -163,7 +163,7 @@ class DynamicEntityTest extends V400ServerSetup {
feature("Update a DynamicEntity v4.0.4- Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities"/ "some-method-routing-id").PUT
val request400 = (v4_0_0_Request / "management" / "dynamic-entities"/ "some-method-routing-id").PUT
val response400 = makePutRequest(request400, write(rightEntity))
Then("We should get a 400")
response400.code should equal(400)
@ -174,7 +174,7 @@ class DynamicEntityTest extends V400ServerSetup {
feature("Get DynamicEntities v4.0.4- Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").GET
val request400 = (v4_0_0_Request / "management" / "dynamic-entities").GET
val response400 = makeGetRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
@ -185,7 +185,7 @@ class DynamicEntityTest extends V400ServerSetup {
feature("Delete the DynamicEntity specified by METHOD_ROUTING_ID v4.0.4- Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / "METHOD_ROUTING_ID").DELETE
val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / "METHOD_ROUTING_ID").DELETE
val response400 = makeDeleteRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
@ -198,7 +198,7 @@ class DynamicEntityTest extends V400ServerSetup {
feature("Add a DynamicEntity v4.0.4- Unauthorized access - Authorized access") {
scenario("We will call the endpoint without the proper Role " + canCreateDynamicEntity, ApiEndpoint1, VersionOfApi) {
When("We make a request v4.0.0 without a Role " + canCreateDynamicEntity)
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1)
val request400 = (v4_0_0_Request / "management" / "dynamic-entities").POST <@(user1)
val response400 = makePostRequest(request400, write(rightEntity))
Then("We should get a 403")
response400.code should equal(403)
@ -209,7 +209,7 @@ class DynamicEntityTest extends V400ServerSetup {
scenario("We will call the endpoint with the proper Role " + canCreateDynamicEntity , ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEntity.toString)
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1)
val request = (v4_0_0_Request / "management" / "dynamic-entities").POST <@(user1)
val response = makePostRequest(request, write(rightEntity))
Then("We should get a 201")
response.code should equal(201)
@ -244,7 +244,7 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update success
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1)
val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1)
val response400 = makePutRequest(request400, compactRender(updateRequest))
Then("We should get a 200")
response400.code should equal(200)
@ -254,7 +254,7 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update a not exists DynamicEntity
val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1)
val request404 = (v4_0_0_Request / "management" / "dynamic-entities" / "not-exists-id" ).PUT <@(user1)
val response404 = makePutRequest(request404, compactRender(updateRequest))
Then("We should get a 404")
response404.code should equal(404)
@ -263,7 +263,7 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update a DynamicEntity with wrong required field name
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1)
val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1)
val response400 = makePutRequest(request400, compactRender(wrongRequiredEntity))
Then("We should get a 400")
@ -273,7 +273,7 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update a DynamicEntity with wrong type of description
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1)
val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1)
val response400 = makePutRequest(request400, compactRender(wrongDescriptionEntity))
Then("We should get a 400")
@ -283,7 +283,7 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update a DynamicEntity with wrong type of property description
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1)
val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1)
val response400 = makePutRequest(request400, compactRender(wrongPropertyDescriptionEntity))
Then("We should get a 400")
@ -293,7 +293,7 @@ class DynamicEntityTest extends V400ServerSetup {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEntities.toString)
When("We make a request v4.0.0 with the Role " + canGetDynamicEntities)
val requestGet = (v4_0_0_Request / "management" / "dynamic_entities").GET <@(user1)
val requestGet = (v4_0_0_Request / "management" / "dynamic-entities").GET <@(user1)
val responseGet = makeGetRequest(requestGet)
Then("We should get a 200")
responseGet.code should equal(200)
@ -308,7 +308,7 @@ class DynamicEntityTest extends V400ServerSetup {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEntity.toString)
When("We make a request v4.0.0 with the Role " + canDeleteDynamicEntity)
val requestDelete400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId).DELETE <@(user1)
val requestDelete400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId).DELETE <@(user1)
val responseDelete400 = makeDeleteRequest(requestDelete400)
Then("We should get a 200")
responseDelete400.code should equal(200)

View File

@ -0,0 +1,284 @@
package code.api.v4_0_0
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole._
import code.api.util.ErrorMessages.{DynamicEndpointExists, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.ExampleValue
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
import code.entitlement.Entitlement
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.JsonAST.JField
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
class DynamicEndpointsTest extends V400ServerSetup {
/**
* Test tags
* Example: To run tests with tag "getPermissions":
* mvn test -D tagsToInclude
*
* This is made possible by the scalatest maven plugin
*/
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createDynamicEndpoint))
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoints))
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoint))
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.deleteDynamicEndpoint))
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").POST
val response400 = makePostRequest(request400, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint1 version $VersionOfApi - authorized access- missing role") {
scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) {
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)
val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 403")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
}
}
feature(s"test $ApiEndpoint1 version $VersionOfApi - authorized access - with role - should be success!") {
scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) {
When("We make a request v4.0.0")
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)
val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 403")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
Then("We grant the role to the user1")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString)
val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 201")
responseWithRole.code should equal(201)
responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true)
responseWithRole.body.toString contains("swagger_string") should be (true)
responseWithRole.body.toString contains("Example Title") should be (true)
responseWithRole.body.toString contains("Example Description") should be (true)
responseWithRole.body.toString contains("Example Company") should be (true)
}
}
feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET
val response400 = makeGetRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access- missing role") {
scenario("We will call the endpoint with user credentials", ApiEndpoint2, VersionOfApi) {
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1)
val response = makeGetRequest(request)
Then("We should get a 400")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
}
}
feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access - with role - should be success!") {
scenario("We will call the endpoint with user credentials", ApiEndpoint2, VersionOfApi) {
When("We make a request v4.0.0")
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)
val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 403")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
Then("We grant the role to the user1")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoints.toString)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString)
val newSwagger = postDynamicEndpointRequestBodyExample.transformField {
case JField(name, value) if name.startsWith("/") => JField(s"$name/abc", value)
}
val responseWithRole = makePostRequest(request, write(newSwagger))
Then("We should get a 201")
responseWithRole.code should equal(201)
val duplicatedRequest = makePostRequest(request, write(newSwagger))
Then("We should get a 400")
duplicatedRequest.code should equal(400)
duplicatedRequest.body.extract[ErrorMessage].message.toString contains (DynamicEndpointExists) should be (true)
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1)
val response400 = makeGetRequest(request400)
response400.code should be (200)
response400.body.toString contains("Example Title") should be (true)
response400.body.toString contains("Example Description") should be (true)
response400.body.toString contains("Example Company") should be (true)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints"/ "some-id").GET
val response400 = makeGetRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access- missing role") {
scenario("We will call the endpoint with user credentials", ApiEndpoint3, VersionOfApi) {
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints" /"some-id").GET<@ (user1)
val response = makeGetRequest(request)
Then("We should get a 400")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access - with role - should be success!") {
scenario("We will call the endpoint with user credentials", ApiEndpoint3, VersionOfApi) {
When("We make a request v4.0.0")
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)
val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 403")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
Then("We grant the role to the user1")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoint.toString)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString)
val newSwagger = postDynamicEndpointRequestBodyExample.transformField {
case JField(name, value) if name.startsWith("/") => JField(s"$name/def", value)
}
val responseWithRole = makePostRequest(request, write(newSwagger))
Then("We should get a 201")
responseWithRole.code should equal(201)
val duplicatedRequest = makePostRequest(request, write(newSwagger))
Then("We should get a 400")
duplicatedRequest.code should equal(400)
duplicatedRequest.body.extract[ErrorMessage].message.toString contains (DynamicEndpointExists) should be (true)
val id = responseWithRole.body.\\("dynamic_endpoint_id").values.get("dynamic_endpoint_id").head.toString
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1)
val response400 = makeGetRequest(request400)
response400.code should be (200)
response400.body.toString contains("dynamic_endpoint_id") should be (true)
response400.body.toString contains("swagger_string") should be (true)
response400.body.toString contains("Example Title") should be (true)
response400.body.toString contains("Example Description") should be (true)
response400.body.toString contains("Example Company") should be (true)
}
}
feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints"/ "some-id").DELETE
val response400 = makeDeleteRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint4 version $VersionOfApi - authorized access- missing role") {
scenario("We will call the endpoint with user credentials", ApiEndpoint4, VersionOfApi) {
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints" /"some-id").DELETE<@ (user1)
val response = makeDeleteRequest(request)
Then("We should get a 400")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
}
}
feature(s"test $ApiEndpoint4 version $VersionOfApi - authorized access - with role - should be success!") {
scenario("We will call the endpoint with user credentials", ApiEndpoint4, VersionOfApi) {
When("We make a request v4.0.0")
val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1)
val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample))
Then("We should get a 403")
response.code should equal(403)
response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true)
Then("We grant the role to the user1")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoint.toString)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEndpoint.toString)
val newSwagger = postDynamicEndpointRequestBodyExample.transformField {
case JField(name, value) if name.startsWith("/") => JField(s"$name/def2", value)
}
val responseWithRole = makePostRequest(request, write(newSwagger))
Then("We should get a 201")
responseWithRole.code should equal(201)
val id = responseWithRole.body.\\("dynamic_endpoint_id").values.get("dynamic_endpoint_id").head.toString
val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1)
val response400 = makeGetRequest(request400)
response400.code should be (200)
response400.body.toString contains("dynamic_endpoint_id") should be (true)
response400.body.toString contains("swagger_string") should be (true)
response400.body.toString contains("Example Title") should be (true)
response400.body.toString contains("Example Description") should be (true)
response400.body.toString contains("Example Company") should be (true)
val requestDelete = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).DELETE<@ (user1)
val responseDelete = makeDeleteRequest(requestDelete)
responseDelete.code should be (204)
val responseGetAgain = makeGetRequest(request400)
responseGetAgain.code should be (404)
}
}
}

View File

@ -6,6 +6,7 @@ import java.net.URI
import code.bankconnectors.rest.RestConnector_vMar2019
import code.connector.RestConnector_vMar2019_FrozenUtil.{connectorMethodNames, persistFilePath, typeNameToFieldsInfo}
import com.openbankproject.commons.util.ReflectUtils
import net.liftweb.common.Logger
import org.apache.commons.io.IOUtils
import org.scalatest.matchers.{MatchResult, Matcher}
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers, Tag}
@ -19,14 +20,19 @@ import scala.reflect.runtime.universe._
class RestConnector_vMar2019_FrozenTest extends FlatSpec with Matchers with BeforeAndAfter {
private var connectorMethodNamesPersisted: List[String] = _
private var typeNameToFieldsInfoPersisted: Map[String, Map[String, String]] = _
private val logger = Logger(classOf[RestConnector_vMar2019_FrozenTest])
before {
val in = new ObjectInputStream(new FileInputStream(persistFilePath))
var in: ObjectInputStream = null
try {
in = new ObjectInputStream(new FileInputStream(persistFilePath))
in.readUTF()
connectorMethodNamesPersisted = in.readObject().asInstanceOf[List[String]]
typeNameToFieldsInfoPersisted = in.readObject().asInstanceOf[Map[String, Map[String, String]]]
} finally {
} catch {
case e: Throwable =>
logger.error("read frozen file fail.", e)
}finally {
IOUtils.closeQuietly(in)
}
}
@ -85,7 +91,7 @@ object RestConnector_vMar2019_FrozenUtil {
.filter(_.overrides.nonEmpty)
.filter(_.paramLists.flatten.nonEmpty)
.map(_.name.toString)
.toList
.toList.filterNot(_ == "dynamicEndpointProcess")
// typeNameToFieldsInfo sturcture is: (typeFullName, Map(fieldName->fieldTypeName))
val typeNameToFieldsInfo: Map[String, Map[String, String]] = {