mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 17:17:09 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
b8ec97ce1b
@ -8,7 +8,7 @@
|
||||
<groupId>com.tesobe</groupId>
|
||||
<artifactId>obp-parent</artifactId>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<version>1.8.2</version>
|
||||
<version>1.9.0</version>
|
||||
</parent>
|
||||
<artifactId>obp-api</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
@ -99,6 +99,8 @@ import code.scope.{MappedScope, MappedUserScope}
|
||||
import code.apicollectionendpoint.ApiCollectionEndpoint
|
||||
import code.apicollection.ApiCollection
|
||||
import code.connectormethod.ConnectorMethod
|
||||
import code.dynamicMessageDoc.DynamicMessageDoc
|
||||
import code.dynamicResourceDoc.DynamicResourceDoc
|
||||
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
|
||||
import code.socialmedia.MappedSocialMedia
|
||||
import code.standingorders.StandingOrder
|
||||
@ -123,7 +125,6 @@ import code.webuiprops.WebUiProps
|
||||
import com.openbankproject.commons.model.ErrorMessage
|
||||
import com.openbankproject.commons.util.Functions.Implicits._
|
||||
import com.openbankproject.commons.util.{ApiVersion, Functions}
|
||||
|
||||
import javax.mail.internet.MimeMessage
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.db.DBLogEntry
|
||||
@ -902,7 +903,9 @@ object ToSchemify {
|
||||
ApiCollectionEndpoint,
|
||||
JsonSchemaValidation,
|
||||
AuthenticationTypeValidation,
|
||||
ConnectorMethod
|
||||
ConnectorMethod,
|
||||
DynamicResourceDoc,
|
||||
DynamicMessageDoc
|
||||
)++ APIBuilder_Connector.allAPIBuilderModels
|
||||
|
||||
// start grpc server
|
||||
|
||||
@ -12,7 +12,8 @@ import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0}
|
||||
import code.api.v2_2_0.{APIMethods220, OBPAPI2_2_0}
|
||||
import code.api.v3_0_0.OBPAPI3_0_0
|
||||
import code.api.v3_1_0.OBPAPI3_1_0
|
||||
import code.api.v4_0_0.{APIMethods400, DynamicEndpointHelper, DynamicEntityHelper, OBPAPI4_0_0}
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
|
||||
import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
|
||||
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
@ -237,7 +238,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
||||
resourceDocTags: Option[List[ResourceDocTag]],
|
||||
partialFunctionNames: Option[List[String]]
|
||||
): Option[JValue] = {
|
||||
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc)
|
||||
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
|
||||
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
|
||||
.map(it => it.specifiedUrl match {
|
||||
case Some(_) => it
|
||||
@ -613,7 +614,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
||||
resourceDocs match {
|
||||
case docs @Some(_) => resourceDocsToJValue(docs)
|
||||
case _ =>
|
||||
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc).toList
|
||||
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs).toList
|
||||
resourceDocsToJValue(Some(dynamicDocs))
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,11 +15,13 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
|
||||
import code.api.v3_0_0.custom.JSONFactoryCustom300
|
||||
import code.api.v3_0_0.{LobbyJsonV330, _}
|
||||
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, CustomerWithAttributesJsonV310, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
|
||||
import code.api.v4_0_0.{APIInfoJson400, AccountBalanceJsonV400, AccountTagJSON, AccountTagsJSON, AccountsBalancesJsonV400, ApiCollectionEndpointJson400, ApiCollectionEndpointsJson400, ApiCollectionJson400, ApiCollectionsJson400, AttributeDefinitionJsonV400, AttributeDefinitionResponseJsonV400, AttributeDefinitionsResponseJsonV400, AttributeJsonV400, BalanceJsonV400, BankAccountRoutingJson, BankJson400, BanksJson400, CallLimitPostJsonV400, ChallengeAnswerJson400, ChallengeJsonV400, CounterpartiesJson400, CounterpartyJson400, CounterpartyWithMetadataJson400, CustomerAttributeJsonV400, CustomerAttributesResponseJson, DirectDebitJsonV400, DoubleEntryTransactionJson, EnergySource400, HostedAt400, HostedBy400, IbanCheckerJsonV400, IbanDetailsJsonV400, JsonSchemaV400, JsonValidationV400, LogoutLinkJson, ModeratedAccountJSON400, ModeratedAccountsJSON400, ModeratedCoreAccountJsonV400, ModeratedFirehoseAccountJsonV400, ModeratedFirehoseAccountsJsonV400, PostAccountAccessJsonV400, PostAccountTagJSON, PostApiCollectionEndpointJson400, PostApiCollectionJson400, PostCounterpartyJson400, PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400, PostRevokeGrantAccountAccessJsonV400, PostStandingOrderJsonV400, PostViewJsonV400, Properties, RefundJson, RevokedJsonV400, SettlementAccountJson, SettlementAccountRequestJson, SettlementAccountResponseJson, SettlementAccountsJson, StandingOrderJsonV400, TransactionAttributeJsonV400, TransactionAttributeResponseJson, TransactionAttributesResponseJson, TransactionBankAccountJson, TransactionRequestAttributeJsonV400, TransactionRequestAttributeResponseJson, TransactionRequestAttributesResponseJson, TransactionRequestBankAccountJson, TransactionRequestBodyRefundJsonV400, TransactionRequestBodySEPAJsonV400, TransactionRequestReasonJsonV400, TransactionRequestRefundFrom, TransactionRequestRefundTo, TransactionRequestWithChargeJSON400, UpdateAccountJsonV400, UserLockStatusJson, When, XxxId}
|
||||
import code.api.v4_0_0.{APIInfoJson400, AccountBalanceJsonV400, AccountTagJSON, AccountTagsJSON, AccountsBalancesJsonV400, ApiCollectionEndpointJson400, ApiCollectionEndpointsJson400, ApiCollectionJson400, ApiCollectionsJson400, AttributeDefinitionJsonV400, AttributeDefinitionResponseJsonV400, AttributeDefinitionsResponseJsonV400, AttributeJsonV400, BalanceJsonV400, BankAccountRoutingJson, BankJson400, BanksJson400, CallLimitPostJsonV400, ChallengeAnswerJson400, ChallengeJsonV400, CounterpartiesJson400, CounterpartyJson400, CounterpartyWithMetadataJson400, CustomerAttributeJsonV400, CustomerAttributesResponseJson, DirectDebitJsonV400, DoubleEntryTransactionJson, EnergySource400, HostedAt400, HostedBy400, IbanCheckerJsonV400, IbanDetailsJsonV400, JsonSchemaV400, JsonValidationV400, LogoutLinkJson, ModeratedAccountJSON400, ModeratedAccountsJSON400, ModeratedCoreAccountJsonV400, ModeratedFirehoseAccountJsonV400, ModeratedFirehoseAccountsJsonV400, PostAccountAccessJsonV400, PostAccountTagJSON, PostApiCollectionEndpointJson400, PostApiCollectionJson400, PostCounterpartyJson400, PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400, PostRevokeGrantAccountAccessJsonV400, PostStandingOrderJsonV400, PostViewJsonV400, Properties, RefundJson, ResourceDocFragment, RevokedJsonV400, SettlementAccountJson, SettlementAccountRequestJson, SettlementAccountResponseJson, SettlementAccountsJson, StandingOrderJsonV400, TransactionAttributeJsonV400, TransactionAttributeResponseJson, TransactionAttributesResponseJson, TransactionBankAccountJson, TransactionRequestAttributeJsonV400, TransactionRequestAttributeResponseJson, TransactionRequestAttributesResponseJson, TransactionRequestBankAccountJson, TransactionRequestBodyRefundJsonV400, TransactionRequestBodySEPAJsonV400, TransactionRequestReasonJsonV400, TransactionRequestRefundFrom, TransactionRequestRefundTo, TransactionRequestWithChargeJSON400, UpdateAccountJsonV400, UserLockStatusJson, When, XxxId}
|
||||
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
|
||||
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
|
||||
import code.consent.ConsentStatus
|
||||
import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody}
|
||||
import code.dynamicMessageDoc.JsonDynamicMessageDoc
|
||||
import code.dynamicResourceDoc.JsonDynamicResourceDoc
|
||||
import code.sandbox.SandboxData
|
||||
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
|
||||
import code.transactionrequests.TransactionRequests.TransactionRequestTypes._
|
||||
@ -29,7 +31,9 @@ import com.openbankproject.commons.model.PinResetReason.{FORGOT, GOOD_SECURITY_P
|
||||
import com.openbankproject.commons.model.enums.{AttributeCategory, CardAttributeType}
|
||||
import com.openbankproject.commons.model.{UserAuthContextUpdateStatus, ViewBasic, _}
|
||||
import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, ReflectUtils, RequiredArgs, RequiredInfo}
|
||||
import net.liftweb.json
|
||||
|
||||
import java.net.URLEncoder
|
||||
import scala.collection.immutable.List
|
||||
|
||||
/**
|
||||
@ -4063,11 +4067,48 @@ object SwaggerDefinitionsJSON {
|
||||
val apiCollectionEndpointJson400 = ApiCollectionEndpointJson400(apiCollectionEndpointIdExample.value, apiCollectionIdExample.value, operationIdExample.value)
|
||||
val apiCollectionEndpointsJson400 = ApiCollectionEndpointsJson400(List(apiCollectionEndpointJson400))
|
||||
|
||||
// the reason of declared as def instead of val: avoid be scanned by allFields field
|
||||
private def getBankMethodBody = "%20%20%20%20%20%20Future.successful%28%0A%20%20%20%20%20%20%20%20Full%28%28BankCommons%28%0A%20%20%20%20%20%20%20%20%20%20BankId%28%22Hello%20bank%20id%22%29%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%228%22%0A%20%20%20%20%20%20%20%20%29%2C%20None%29%29%0A%20%20%20%20%20%20%29"
|
||||
val jsonConnectorMethod = JsonConnectorMethod(Some(""),"getBank", getBankMethodBody)
|
||||
val jsonConnectorMethodMethodBody = JsonConnectorMethodMethodBody(getBankMethodBody)
|
||||
val jsonConnectorMethod = JsonConnectorMethod(Some(connectorMethodIdExample.value),"getBank", connectorMethodBodyExample.value)
|
||||
val jsonConnectorMethodMethodBody = JsonConnectorMethodMethodBody(connectorMethodBodyExample.value)
|
||||
|
||||
val jsonDynamicResourceDoc = JsonDynamicResourceDoc(
|
||||
dynamicResourceDocId = Some(dynamicResourceDocIdExample.value),
|
||||
methodBody = dynamicResourceDocMethodBodyExample.value,
|
||||
partialFunctionName = dynamicResourceDocPartialFunctionNameExample.value,
|
||||
requestVerb = requestVerbExample.value,
|
||||
requestUrl = requestUrlExample.value,
|
||||
summary = dynamicResourceDocSummaryExample.value,
|
||||
description = dynamicResourceDocdescriptionExample.value,
|
||||
exampleRequestBody = Option(json.parse(exampleRequestBodyExample.value)),
|
||||
successResponseBody = Option(json.parse(successResponseBodyExample.value)),
|
||||
errorResponseBodies = errorResponseBodiesExample.value,
|
||||
tags = tagsExample.value,
|
||||
roles = rolesExample.value
|
||||
)
|
||||
|
||||
val jsonDynamicMessageDoc = JsonDynamicMessageDoc(
|
||||
dynamicMessageDocId = Some(dynamicMessageDocIdExample.value),
|
||||
process = processExample.value,
|
||||
messageFormat = messageFormatExample.value,
|
||||
description = descriptionExample.value,
|
||||
outboundTopic = outboundTopicExample.value,
|
||||
inboundTopic = inboundTopicExample.value,
|
||||
exampleOutboundMessage = json.parse(exampleOutboundMessageExample.value),
|
||||
exampleInboundMessage = json.parse(exampleInboundMessageExample.value),
|
||||
outboundAvroSchema = outboundAvroSchemaExample.value,
|
||||
inboundAvroSchema = inboundAvroSchemaExample.value,
|
||||
adapterImplementation = adapterImplementationExample.value,
|
||||
methodBody = connectorMethodBodyExample.value
|
||||
)
|
||||
|
||||
val jsonResourceDocFragment = ResourceDocFragment(
|
||||
requestVerbExample.value,
|
||||
requestUrlExample.value,
|
||||
exampleRequestBody = Option(json.parse(exampleRequestBodyExample.value)),
|
||||
successResponseBody = Option(json.parse(successResponseBodyExample.value))
|
||||
)
|
||||
|
||||
val jsonCodeTemplate = "code" -> URLEncoder.encode("""println("hello")""", "UTF-8")
|
||||
|
||||
//The common error or success format.
|
||||
//Just some helper format to use in Json
|
||||
case class NotSupportedYet()
|
||||
|
||||
@ -41,12 +41,13 @@ import code.api.builder.OBP_APIBuilder
|
||||
import code.api.oauth1a.Arithmetics
|
||||
import code.api.oauth1a.OauthParams._
|
||||
import code.api.sandbox.SandboxApiCalls
|
||||
import code.api.util.APIUtil.ResourceDoc.{findPathVariableNames, isPathVariable}
|
||||
import code.api.util.ApiTag.{ResourceDocTag, apiTagBank, apiTagNewStyle}
|
||||
import code.api.util.Glossary.GlossaryItem
|
||||
import code.api.util.RateLimitingJson.CallLimit
|
||||
import code.api.v1_2.ErrorMessage
|
||||
import code.api.{DirectLogin, _}
|
||||
import code.api.v4_0_0.{DynamicEndpointHelper, DynamicEntityHelper}
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
|
||||
import code.authtypevalidation.AuthenticationTypeValidationProvider
|
||||
import code.bankconnectors.Connector
|
||||
import code.consumer.Consumers
|
||||
@ -1145,6 +1146,32 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
// So create the EmptyClassJson to set the empty JValue "{}"
|
||||
case class EmptyClassJson(jsonString: String ="{}")
|
||||
|
||||
/**
|
||||
* PrimaryDataBody is used to make the following ResourceDoc.exampleRequestBody and ResourceDoc.successResponseBody to
|
||||
* support the primitive types(String, Int, Boolean....)
|
||||
* also see@ case class ResourceDoc -> exampleRequestBody: scala.Product and successResponseBody: scala.Product
|
||||
* It is `product` type, not support the primitive scala types.
|
||||
*
|
||||
* following are some usages eg:
|
||||
* 1st: we support empty string for `Create Dynamic Resource Doc` endpoint, json body.example_request_body = "",
|
||||
* 2rd: Swagger file use Boolean as response body.
|
||||
* .....
|
||||
*
|
||||
*
|
||||
* Here are the sub-classes of PrimaryDataBody, they can be used for scala.Product type.
|
||||
* JArrayBody in APIUtil$ (code.api.util)
|
||||
* FloatBody in APIUtil$ (code.api.util)
|
||||
* IntBody in APIUtil$ (code.api.util)
|
||||
* DoubleBody in APIUtil$ (code.api.util)
|
||||
* BigDecimalBody in APIUtil$ (code.api.util)
|
||||
* BooleanBody in APIUtil$ (code.api.util)
|
||||
* StringBody in APIUtil$ (code.api.util)
|
||||
* LongBody in APIUtil$ (code.api.util)
|
||||
* BigIntBody in APIUtil$ (code.api.util)
|
||||
* EmptyBody$ in APIUtil$ (code.api.util)
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
sealed abstract class PrimaryDataBody[T] extends JsonAble {
|
||||
def value: T
|
||||
|
||||
@ -1194,7 +1221,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case class JArrayBody(value: JArray) extends PrimaryDataBody[JArray]
|
||||
|
||||
/**
|
||||
* Any dynamic endpoint'ResourceDoc, it's partialFunction should set this stub endpoint.
|
||||
* Any dynamic endpoint's ResourceDoc, it's partialFunction should set this stub endpoint.
|
||||
*/
|
||||
val dynamicEndpointStub: OBPEndpoint = Functions.doNothing
|
||||
|
||||
@ -1202,7 +1229,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
private val operationIdToResourceDoc: ConcurrentHashMap[String, ResourceDoc] = new ConcurrentHashMap[String, ResourceDoc]
|
||||
|
||||
def getResourceDocs(operationIds: List[String]): List[ResourceDoc] = {
|
||||
val dynamicDocs = DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc
|
||||
val dynamicDocs = DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs
|
||||
operationIds.collect {
|
||||
case operationId if operationIdToResourceDoc.containsKey(operationId) =>
|
||||
operationIdToResourceDoc.get(operationId)
|
||||
@ -1210,6 +1237,16 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
dynamicDocs.find(_.operationId == operationId).orNull
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether url part is path variable
|
||||
* @param urlFragment
|
||||
* @return e.g: the url is /abc/ABC_ID/hello, ABC_ID is path variable
|
||||
*/
|
||||
def isPathVariable(urlFragment: String) = urlFragment == urlFragment.toUpperCase()
|
||||
|
||||
def findPathVariableNames(url: String) = StringUtils.split(url, '/')
|
||||
.filter(isPathVariable(_))
|
||||
}
|
||||
|
||||
// Used to document the API calls
|
||||
@ -1342,17 +1379,19 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
|
||||
private val requestUrlPartPath: Array[String] = StringUtils.split(requestUrl, '/')
|
||||
|
||||
private val requestUrlBankId = requestUrlPartPath.indexOf("BANK_ID")
|
||||
private val requestUrlAccountId = requestUrlPartPath.indexOf("ACCOUNT_ID")
|
||||
private val requestUrlViewId = requestUrlPartPath.indexOf("VIEW_ID")
|
||||
|
||||
private val isNeedCheckAuth = errorResponseBodies.contains($UserNotLoggedIn)
|
||||
private val isNeedCheckRoles = _autoValidateRoles && rolesForCheck.nonEmpty
|
||||
private val isNeedCheckBank = errorResponseBodies.contains($BankNotFound) && requestUrlBankId != -1
|
||||
private val isNeedCheckBank = errorResponseBodies.contains($BankNotFound) && requestUrlPartPath.contains("BANK_ID")
|
||||
private val isNeedCheckAccount = errorResponseBodies.contains($BankAccountNotFound) &&
|
||||
requestUrlBankId != -1 && requestUrlAccountId != -1
|
||||
requestUrlPartPath.contains("BANK_ID") && requestUrlPartPath.contains("ACCOUNT_ID")
|
||||
private val isNeedCheckView = errorResponseBodies.contains($UserNoPermissionAccessView) &&
|
||||
requestUrlBankId != -1 && requestUrlAccountId != -1 && requestUrlViewId != -1
|
||||
requestUrlPartPath.contains("BANK_ID") && requestUrlPartPath.contains("ACCOUNT_ID") && requestUrlPartPath.contains("VIEW_ID")
|
||||
|
||||
private val reversedRequestUrl = requestUrlPartPath.reverse
|
||||
def getPathParams(url: List[String]): Map[String, String] =
|
||||
reversedRequestUrl.zip(url.reverse) collect {
|
||||
case pair @(k, _) if isPathVariable(k) => pair
|
||||
} toMap
|
||||
|
||||
/**
|
||||
* According errorResponseBodies whether contains UserNotLoggedIn and UserHasMissingRoles do validation.
|
||||
@ -1370,29 +1409,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
def wrappedWithAuthCheck(obpEndpoint: OBPEndpoint): OBPEndpoint = {
|
||||
_isEndpointAuthCheck = true
|
||||
|
||||
def getIds(url: List[String]): (Option[BankId], Option[AccountId], Option[ViewId]) = {
|
||||
val apiPrefixLength = url.size - requestUrlPartPath.size
|
||||
|
||||
val bankId = if (requestUrlBankId != -1) {
|
||||
url.lift(apiPrefixLength + requestUrlBankId).map(BankId(_))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
val accountId = if (bankId.isDefined && requestUrlAccountId != -1) {
|
||||
url.lift(apiPrefixLength + requestUrlAccountId).map(AccountId(_))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
val viewId = if (accountId.isDefined && requestUrlViewId != -1) {
|
||||
url.lift(apiPrefixLength + requestUrlViewId).map(ViewId(_))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
(bankId, accountId, viewId)
|
||||
}
|
||||
|
||||
def checkAuth(cc: CallContext): Future[(Box[User], Option[CallContext])] = {
|
||||
if (isNeedCheckAuth) authenticatedAccessFun(cc) else anonymousAccessFun(cc)
|
||||
}
|
||||
@ -1469,39 +1485,32 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
|
||||
val isUrlMatchesResourceDocUrl: List[String] => Boolean = {
|
||||
val urlInDoc = StringUtils.split(this.requestUrl, '/')
|
||||
val indices = urlInDoc.indices
|
||||
// all path parameter indices
|
||||
// whether url part is path parameter, e.g: BAR_ID in the path /obp/v4.0.0/foo/bar/BAR_ID
|
||||
val pathParamIndices = {
|
||||
for {
|
||||
index <- indices
|
||||
urlPart = urlInDoc(index)
|
||||
if urlPart.toUpperCase == urlPart
|
||||
} yield index
|
||||
}.toSet
|
||||
val pathVariableNames = findPathVariableNames(this.requestUrl)
|
||||
|
||||
(requestUrl: List[String]) => {
|
||||
if (requestUrl == urlInDoc) {
|
||||
true
|
||||
} else {
|
||||
(requestUrl.size == urlInDoc.size) &&
|
||||
indices.forall { index =>
|
||||
val requestUrlPart = requestUrl(index)
|
||||
val docUrlPart = urlInDoc(index)
|
||||
requestUrlPart == docUrlPart || pathParamIndices.contains(index)
|
||||
urlInDoc.zip(requestUrl).forall {
|
||||
case (k, v) =>
|
||||
k == v || pathVariableNames.contains(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new OBPEndpoint {
|
||||
override def isDefinedAt(x: Req): Boolean = obpEndpoint.isDefinedAt(x) && isUrlMatchesResourceDocUrl(x.path.partPath)
|
||||
override def isDefinedAt(x: Req): Boolean =
|
||||
obpEndpoint.isDefinedAt(x) && isUrlMatchesResourceDocUrl(x.path.partPath)
|
||||
|
||||
override def apply(req: Req): CallContext => Box[JsonResponse] = {
|
||||
val originFn: CallContext => Box[JsonResponse] = obpEndpoint.apply(req)
|
||||
|
||||
|
||||
val (bankId, accountId, viewId) = getIds(req.path.partPath)
|
||||
val pathParams = getPathParams(req.path.partPath)
|
||||
val bankId = pathParams.get("BANK_ID").map(BankId(_))
|
||||
val accountId = pathParams.get("ACCOUNT_ID").map(AccountId(_))
|
||||
val viewId = pathParams.get("VIEW_ID").map(ViewId(_))
|
||||
|
||||
val request: Box[Req] = S.request
|
||||
val session: Box[LiftSession] = S.session
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package code.api.util
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import code.api.v4_0_0.{DynamicEndpointHelper, DynamicEntityHelper}
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityHelper}
|
||||
import com.openbankproject.commons.util.{JsonAble, ReflectUtils}
|
||||
import net.liftweb.json.{Formats, JsonAST}
|
||||
import net.liftweb.json.JsonDSL._
|
||||
@ -662,6 +662,36 @@ object ApiRole {
|
||||
|
||||
case class CanGetAllConnectorMethods(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllConnectorMethods = CanGetAllConnectorMethods()
|
||||
|
||||
case class CanCreateDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canCreateDynamicResourceDoc = CanCreateDynamicResourceDoc()
|
||||
|
||||
case class CanUpdateDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateDynamicResourceDoc = CanUpdateDynamicResourceDoc()
|
||||
|
||||
case class CanGetDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetDynamicResourceDoc = CanGetDynamicResourceDoc()
|
||||
|
||||
case class CanGetAllDynamicResourceDocs(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllDynamicResourceDocs = CanGetAllDynamicResourceDocs()
|
||||
|
||||
case class CanDeleteDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canDeleteDynamicResourceDoc = CanDeleteDynamicResourceDoc()
|
||||
|
||||
case class CanCreateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canCreateDynamicMessageDoc = CanCreateDynamicMessageDoc()
|
||||
|
||||
case class CanUpdateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateDynamicMessageDoc = CanUpdateDynamicMessageDoc()
|
||||
|
||||
case class CanGetDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetDynamicMessageDoc = CanGetDynamicMessageDoc()
|
||||
|
||||
case class CanGetAllDynamicMessageDocs(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllDynamicMessageDocs = CanGetAllDynamicMessageDocs()
|
||||
|
||||
case class CanDeleteDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canDeleteDynamicMessageDoc = CanDeleteDynamicMessageDoc()
|
||||
|
||||
private val dynamicApiRoles = new ConcurrentHashMap[String, ApiRole]
|
||||
|
||||
|
||||
@ -69,7 +69,9 @@ object ApiTag {
|
||||
val apiTagMethodRouting = ResourceDocTag("Method-Routing")
|
||||
val apiTagWebUiProps = ResourceDocTag("WebUi-Props")
|
||||
val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-(Manage)")
|
||||
val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-(Manage)")
|
||||
val apiTagDynamicSwaggerDoc = ResourceDocTag("Dynamic-Swagger-Doc-(Manage)")
|
||||
val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-(Manage)")
|
||||
val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc-(Manage)")
|
||||
val apiTagApiCollection = ResourceDocTag("Api-Collection")
|
||||
|
||||
val apiTagDynamic = ResourceDocTag("Dynamic")
|
||||
|
||||
@ -21,7 +21,7 @@ trait CustomJsonFormats {
|
||||
|
||||
object CustomJsonFormats {
|
||||
|
||||
val formats: Formats = JsonSerializers.commonFormats + JsonAbleSerializer
|
||||
val formats: Formats = JsonSerializers.commonFormats + PrimaryDataBodySerializer + ToStringDeSerializer + TupleSerializer
|
||||
|
||||
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless ++ JsonSerializers.serializers
|
||||
|
||||
@ -37,10 +37,9 @@ object CustomJsonFormats {
|
||||
}
|
||||
|
||||
|
||||
object OptionalFieldSerializer extends Serializer[AnyRef] {
|
||||
object OptionalFieldSerializer extends ObpSerializer[AnyRef] {
|
||||
private val typedOptionalPathRegx = "(.+?):(.+)".r
|
||||
private val memo = new Memo[universe.Type, List[String]]()
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, json.JValue), AnyRef] = Functions.doNothing
|
||||
private lazy val propsOutboundOptionalFields =
|
||||
APIUtil.getPropsValue("outbound.optional.fields", "")
|
||||
.split("""\s*,\s*""").filterNot(StringUtils.isBlank).toList
|
||||
@ -125,11 +124,9 @@ object OptionalFieldSerializer extends Serializer[AnyRef] {
|
||||
}
|
||||
|
||||
|
||||
object JsonAbleSerializer extends Serializer[PrimaryDataBody[_]] {
|
||||
object PrimaryDataBodySerializer extends ObpDeSerializer[PrimaryDataBody[_]] {
|
||||
private val IntervalClass = classOf[Product]
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, json.JValue), PrimaryDataBody[_]] = {
|
||||
case (TypeInfo(IntervalClass, _), json) if !json.isInstanceOf[JObject] => json match {
|
||||
case JNothing => EmptyBody
|
||||
@ -141,4 +138,27 @@ object JsonAbleSerializer extends Serializer[PrimaryDataBody[_]] {
|
||||
case x => throw new MappingException("Can't convert " + x + " to PrimaryDataBody")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deserialize jvalue to string, even if jvalue is not JString type, it can deserialize successfully.
|
||||
*/
|
||||
object ToStringDeSerializer extends ObpDeSerializer[String] {
|
||||
private val IntervalClass = classOf[String]
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, json.JValue), String] = {
|
||||
case (TypeInfo(IntervalClass, _), json) if !json.isInstanceOf[JString] && json != JNothing =>
|
||||
compactRender(json)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deserialize jvalue to string, even if jvalue is not JString type, it can deserialize successfully.
|
||||
*/
|
||||
object TupleSerializer extends ObpSerializer[(String, Any)] {
|
||||
import net.liftweb.json.JsonDSL._
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, json.JValue] = {
|
||||
case (name: String, value: Any) =>
|
||||
name -> json.Extraction.decompose(value)
|
||||
}
|
||||
}
|
||||
167
obp-api/src/main/scala/code/api/util/DynamicUtil.scala
Normal file
167
obp-api/src/main/scala/code/api/util/DynamicUtil.scala
Normal file
@ -0,0 +1,167 @@
|
||||
package code.api.util
|
||||
import com.openbankproject.commons.util.JsonUtils
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import net.liftweb.json.{JObject, JValue, prettyRender}
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import scala.reflect.runtime.universe
|
||||
import scala.reflect.runtime.universe.runtimeMirror
|
||||
import scala.tools.reflect.{ToolBox, ToolBoxError}
|
||||
|
||||
object DynamicUtil {
|
||||
|
||||
val toolBox: ToolBox[universe.type] = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
||||
|
||||
// code -> dynamic method function
|
||||
// the same code should always be compiled once, so here cache them
|
||||
private val dynamicCompileResult = new ConcurrentHashMap[String, Box[Any]]()
|
||||
/**
|
||||
* Compile scala code
|
||||
* toolBox have bug that first compile fail, second or later compile success.
|
||||
* @param code
|
||||
* @return compiled Full[function|object|class] or Failure
|
||||
*/
|
||||
def compileScalaCode[T](code: String): Box[T] = {
|
||||
val compiledResult: Box[Any] = dynamicCompileResult.computeIfAbsent(code, _ => {
|
||||
val tree = try {
|
||||
toolBox.parse(code)
|
||||
} catch {
|
||||
case e: ToolBoxError =>
|
||||
return Failure(e.message)
|
||||
}
|
||||
|
||||
try {
|
||||
val func: () => Any = toolBox.compile(tree)
|
||||
Box.tryo(func())
|
||||
} catch {
|
||||
case _: ToolBoxError =>
|
||||
// try compile again
|
||||
try {
|
||||
val func: () => Any = toolBox.compile(tree)
|
||||
Box.tryo(func())
|
||||
} catch {
|
||||
case e: ToolBoxError =>
|
||||
Failure(e.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
compiledResult.map(_.asInstanceOf[T])
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param methodName the method name
|
||||
* @param function the method body, if it is empty, then throw exception. if it is existing, then call this function.
|
||||
* @param args the method parameters
|
||||
* @return the result of the execution of the function.
|
||||
*/
|
||||
def executeFunction(methodName: String, function: Box[Any], args: Array[AnyRef]) = {
|
||||
val result = function.orNull match {
|
||||
case func: Function0[AnyRef] => func()
|
||||
case func: Function[AnyRef, AnyRef] => func(args.head)
|
||||
case func: Function2[AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1))
|
||||
case func: Function3[AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2))
|
||||
case func: Function4[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3))
|
||||
case func: Function5[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4))
|
||||
case func: Function6[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5))
|
||||
case func: Function7[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6))
|
||||
case func: Function8[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7))
|
||||
case func: Function9[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8))
|
||||
case func: Function10[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9))
|
||||
case func: Function11[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10))
|
||||
case func: Function12[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11))
|
||||
case func: Function13[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12))
|
||||
case func: Function14[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13))
|
||||
case func: Function15[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14))
|
||||
case func: Function16[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15))
|
||||
case func: Function17[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16))
|
||||
case func: Function18[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17))
|
||||
case func: Function19[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17), args.apply(18))
|
||||
case func: Function20[AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17), args.apply(18), args.apply(19))
|
||||
case null => throw new IllegalStateException(s"There is no method $methodName, it should not be called here")
|
||||
case _ => throw new IllegalStateException(s"$methodName can not be called here.")
|
||||
}
|
||||
result.asInstanceOf[AnyRef]
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will create a object from the JValue.
|
||||
* from JValue --> Case Class String --> DynamicUtil.compileScalaCode(code) --> object
|
||||
* @param jValue
|
||||
* @return
|
||||
*/
|
||||
def toCaseObject(jValue: JValue): Product = {
|
||||
val caseClasses = JsonUtils.toCaseClasses(jValue)
|
||||
val code =
|
||||
s"""
|
||||
| $caseClasses
|
||||
|
|
||||
| // throws exception: net.liftweb.json.MappingException:
|
||||
| //No usable value for name
|
||||
| //Did not find value which can be converted into java.lang.String
|
||||
|
|
||||
|implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
|(jValue: net.liftweb.json.JsonAST.JValue) => {
|
||||
| jValue.extract[RootJsonClass]
|
||||
|}
|
||||
|""".stripMargin
|
||||
val fun: Box[JValue => Product] = DynamicUtil.compileScalaCode(code)
|
||||
fun match {
|
||||
case Full(func) => func.apply(jValue)
|
||||
case Failure(msg: String, exception: Box[Throwable], _) =>
|
||||
throw exception.getOrElse(new RuntimeException(msg))
|
||||
case _ => throw new RuntimeException(s"Json extract to case object fail, json: \n ${prettyRender(jValue)}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* common import statements those are used by compiler
|
||||
*/
|
||||
val importStatements =
|
||||
"""
|
||||
|import java.net.{ConnectException, URLEncoder, UnknownHostException}
|
||||
|import java.util.Date
|
||||
|import java.util.UUID.randomUUID
|
||||
|
|
||||
|import _root_.akka.stream.StreamTcpException
|
||||
|import akka.http.scaladsl.model.headers.RawHeader
|
||||
|import akka.http.scaladsl.model.{HttpProtocol, _}
|
||||
|import akka.util.ByteString
|
||||
|import code.api.APIFailureNewStyle
|
||||
|import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions
|
||||
|import code.api.cache.Caching
|
||||
|import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType, saveConnectorMetric, _}
|
||||
|import code.api.util.ErrorMessages._
|
||||
|import code.api.util.ExampleValue._
|
||||
|import code.api.util.{APIUtil, CallContext, OBPQueryParam}
|
||||
|import code.api.v4_0_0.dynamic.MockResponseHolder
|
||||
|import code.bankconnectors._
|
||||
|import code.bankconnectors.vJune2017.AuthInfo
|
||||
|import code.customer.internalMapping.MappedCustomerIdMappingProvider
|
||||
|import code.kafka.KafkaHelper
|
||||
|import code.model.dataAccess.internalMapping.MappedAccountIdMappingProvider
|
||||
|import code.util.AkkaHttpClient._
|
||||
|import code.util.Helper.MdcLoggable
|
||||
|import com.openbankproject.commons.dto.{InBoundTrait, _}
|
||||
|import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
|
||||
|import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
|
||||
|import com.openbankproject.commons.model.{ErrorMessage, TopicTrait, _}
|
||||
|import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
|// import com.tesobe.{CacheKeyFromArguments, CacheKeyOmit}
|
||||
|import net.liftweb.common.{Box, Empty, _}
|
||||
|import net.liftweb.json
|
||||
|import net.liftweb.json.Extraction.decompose
|
||||
|import net.liftweb.json.JsonDSL._
|
||||
|import net.liftweb.json.JsonParser.ParseException
|
||||
|import net.liftweb.json.{JValue, _}
|
||||
|import net.liftweb.util.Helpers.tryo
|
||||
|import org.apache.commons.lang3.StringUtils
|
||||
|
|
||||
|import scala.collection.immutable.List
|
||||
|import scala.collection.mutable.ArrayBuffer
|
||||
|import scala.concurrent.duration._
|
||||
|import scala.concurrent.{Await, Future}
|
||||
|import com.openbankproject.commons.dto._
|
||||
|""".stripMargin
|
||||
}
|
||||
@ -470,9 +470,15 @@ object ErrorMessages {
|
||||
|
||||
val ConnectorMethodNotFound = "OBP-40036: ConnectorMethod not found, please specify valid CONNECTOR_METHOD_ID. "
|
||||
val ConnectorMethodAlreadyExists = "OBP-40037: ConnectorMethod already exists. "
|
||||
val ConnectorMethodBodyCompileFail = "OBP-40038: ConnectorMethod methodBody is illegal scala code, compile fail. "
|
||||
|
||||
val ConnectorMethodBodyCompileFail = "OBP-40038: ConnectorMethod methodBody is illegal scala code, compilation failed. "
|
||||
val DynamicResourceDocAlreadyExists = "OBP-40039: DynamicResourceDoc already exists."
|
||||
val DynamicResourceDocNotFound = "OBP-40040: DynamicResourceDoc not found, please specify valid DYNAMIC_RESOURCE_DOC_ID. "
|
||||
val DynamicResourceDocDeleteError = "OBP-40041: DynamicResourceDoc can not be deleted. "
|
||||
|
||||
val DynamicMessageDocAlreadyExists = "OBP-40042: DynamicMessageDoc already exists."
|
||||
val DynamicMessageDocNotFound = "OBP-40043: DynamicMessageDoc not found, please specify valid DYNAMIC_MESSAGE_DOC_ID. "
|
||||
val DynamicMessageDocDeleteError = "OBP-40044: DynamicMessageDoc can not be deleted. "
|
||||
val DynamicCodeCompileFail = "OBP-40045: The code to do compile is illegal scala code, compilation failed. "
|
||||
// Exceptions (OBP-50XXX)
|
||||
val UnknownError = "OBP-50000: Unknown Error."
|
||||
val FutureTimeoutException = "OBP-50001: Future Timeout Exception."
|
||||
|
||||
@ -2,6 +2,7 @@ package code.api.util
|
||||
|
||||
|
||||
import code.api.util.APIUtil.parseDate
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, UserHasMissingRoles, UserNotLoggedIn, UnknownError}
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import code.api.util.Glossary.{glossaryItems, makeGlossaryItem}
|
||||
import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample}
|
||||
@ -370,6 +371,85 @@ object ExampleValue {
|
||||
lazy val htmlExample = ConnectorField("html format content","the content is displayed in HTML format")
|
||||
glossaryItems += makeGlossaryItem("html", htmlExample)
|
||||
|
||||
|
||||
lazy val connectorMethodIdExample = ConnectorField("ace0352a-9a0f-4bfa-b30b-9003aa467f51", "A string that MUST uniquely identify the connector method on this OBP instance, can be used in all cache. ")
|
||||
glossaryItems += makeGlossaryItem("ConnectorMethod.connectorMethodId", connectorMethodIdExample)
|
||||
|
||||
lazy val dynamicResourceDocMethodBodyExample = ConnectorField("%20%20%20%20val%20Some(resourceDoc)%20%3D%20callContext.resourceDocument%0A%20%20%20%20" +
|
||||
"val%20hasRequestBody%20%3D%20request.body.isDefined%0A%0A%20%20%20%20%2F%2F%20get%20Path%20Parameters%2C%20example%3A%0A%20%20%20" +
|
||||
"%20%2F%2F%20if%20the%20requestUrl%20of%20resourceDoc%20is%20%2Fhello%2Fbanks%2FBANK_ID%2Fworld%0A%20%20%20%20%2F%2F%20the%20reque" +
|
||||
"st%20path%20is%20%2Fhello%2Fbanks%2Fbank_x%2Fworld%0A%20%20%20%20%2F%2FpathParams.get(%22BANK_ID%22)%20will%20get%20Option(%22bank" +
|
||||
"_x%22)%20value%0A%20%20%20%20val%20pathParams%20%3D%20getPathParams(callContext%2C%20request)%0A%20%20%20%20val%20myUserId%20%3D%2" +
|
||||
"0pathParams(%22MY_USER_ID%22)%0A%0A%0A%20%20%20%20val%20requestEntity%20%3D%20request.json%20match%20%7B%0A%20%20%20%20%20%20case%" +
|
||||
"20Full(zson)%20%3D%3E%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20zson.extract%5BRequestRootJsonClass%5D%0" +
|
||||
"A%20%20%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20%20%20case%20e%3A%20MappingException%20%3D%3E%0A%20%20%20%20%" +
|
||||
"20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidJsonFormat%20%24%7Be.msg%7D%22))%0A%20%20%20%20%20%20%20%20%7" +
|
||||
"D%0A%20%20%20%20%20%20case%20_%3A%20EmptyBox%20%3D%3E%0A%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidRequ" +
|
||||
"estPayload%20Current%20request%20has%20no%20payload%22))%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%2F%2F%20please%20add%20business%20" +
|
||||
"logic%20here%0A%20%20%20%20val%20responseBody%3AResponseRootJsonClass%20%3D%20ResponseRootJsonClass(s%22%24%7BmyUserId%7D_from_pat" +
|
||||
"h%22%2C%20requestEntity.name%2C%20requestEntity.age%2C%20requestEntity.hobby)%0A%20%20%20%20Future.successful%20%7B%0A%20%20%20%20" +
|
||||
"%20%20(responseBody%2C%20HttpCode.%60200%60(callContext.callContext))%0A%20%20%20%20%7D",
|
||||
"the URL-encoded format String, the original code is the OBP connector method body.")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.methodBody", dynamicResourceDocMethodBodyExample)
|
||||
|
||||
lazy val connectorMethodBodyExample = ConnectorField("%20%20%20%20%20%20Future.successful%28%0A%20%20%20%20%20%20%20%20Full%28%" +
|
||||
"28BankCommons%28%0A%20%20%20%20%20%20%20%20%20%20BankId%28%22Hello%20bank%20id%22%29%2C%0A%20%20%20%20%20%20%20%20%20" +
|
||||
"%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%2" +
|
||||
"0%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%2" +
|
||||
"0%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%228%22%0A%20%20%20%20%20%20%20%20%29%2C%20None%29%29%0A%" +
|
||||
"20%20%20%20%20%20%29",
|
||||
"the URL-encoded format String, the original code is the OBP connector method body.")
|
||||
glossaryItems += makeGlossaryItem("DynamicConnectorMethod.methodBody", connectorMethodBodyExample)
|
||||
|
||||
lazy val dynamicResourceDocIdExample = ConnectorField("vce035ca-9a0f-4bfa-b30b-9003aa467f51", "A string that MUST uniquely identify the dynamic Resource Doc on this OBP instance, can be used in all cache. ")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.dynamicResourceDocId", dynamicResourceDocIdExample)
|
||||
|
||||
lazy val partialFunctionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.partialFunction", partialFunctionExample)
|
||||
|
||||
lazy val implementedInApiVersionExample = ConnectorField(NoExampleProvided, NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.implementedInApiVersion", implementedInApiVersionExample)
|
||||
|
||||
lazy val partialFunctionNameExample = ConnectorField("getBanks", "partial function name")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.partialFunctionName", partialFunctionNameExample)
|
||||
|
||||
lazy val dynamicResourceDocPartialFunctionNameExample = ConnectorField("createUser", "partial function name")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.partialFunctionName", dynamicResourceDocPartialFunctionNameExample)
|
||||
|
||||
lazy val requestVerbExample = ConnectorField("POST", "This is the HTTP methods, eg: GET, POST, PUT, DELETE ")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.requestVerb", requestVerbExample)
|
||||
|
||||
lazy val requestUrlExample = ConnectorField("/my_user/MY_USER_ID", "The URL of the endpoint.")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.requestUrl", requestUrlExample)
|
||||
|
||||
lazy val exampleRequestBodyExample = ConnectorField("""{"name": "Jhon", "age": 12, "hobby": ["coding"],"_optional_fields_": ["hobby"]}""", "the json string of the request body.")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.exampleRequestBody", exampleRequestBodyExample)
|
||||
|
||||
lazy val successResponseBodyExample = ConnectorField("""{"my_user_id": "some_id_value", "name": "Jhon", "age": 12, "hobby": ["coding"],"_optional_fields_": ["hobby"]}""".stripMargin, "the json string of the success response body.")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.successResponseBody", successResponseBodyExample)
|
||||
|
||||
lazy val errorResponseBodiesExample = ConnectorField(s"$UnknownError,$UserNotLoggedIn,$UserHasMissingRoles,$InvalidJsonFormat", "The possible error messages of the endpoint. ")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.errorResponseBodies", errorResponseBodiesExample)
|
||||
|
||||
|
||||
lazy val isFeaturedExample = ConnectorField("false", "if this is featured or not ")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.isFeatured", isFeaturedExample)
|
||||
|
||||
lazy val specialInstructionsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.specialInstructions", specialInstructionsExample)
|
||||
|
||||
lazy val specifiedUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.specifiedUrl", specifiedUrlExample)
|
||||
|
||||
lazy val dynamicMessageDocIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicMessageDoc.dynamicMessageDocId", dynamicMessageDocIdExample)
|
||||
|
||||
lazy val outboundAvroSchemaExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicMessageDoc.outboundAvroSchema", outboundAvroSchemaExample)
|
||||
|
||||
lazy val inboundAvroSchemaExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("DynamicMessageDoc.inboundAvroSchema", inboundAvroSchemaExample)
|
||||
|
||||
lazy val canSeeImagesExample = ConnectorField("true",NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("can_see_images", canSeeImagesExample)
|
||||
|
||||
@ -475,7 +555,7 @@ object ExampleValue {
|
||||
lazy val ownersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("owners", ownersExample)
|
||||
|
||||
lazy val exampleInboundMessageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
lazy val exampleInboundMessageExample = ConnectorField("{}", "This is the json object.")
|
||||
glossaryItems += makeGlossaryItem("example_inbound_message", exampleInboundMessageExample)
|
||||
|
||||
lazy val nationalIdentifierExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
@ -844,7 +924,7 @@ object ExampleValue {
|
||||
lazy val perMonthCallLimitExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("per_month_call_limit", perMonthCallLimitExample)
|
||||
|
||||
lazy val rolesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
lazy val rolesExample = ConnectorField("CanCreateMyUser","Entitlements are used to grant System or Bank level roles to Users ")
|
||||
glossaryItems += makeGlossaryItem("roles", rolesExample)
|
||||
|
||||
lazy val categoryExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
@ -1120,7 +1200,7 @@ object ExampleValue {
|
||||
lazy val counterpartyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("counterparty", counterpartyExample)
|
||||
|
||||
lazy val tagsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
lazy val tagsExample = ConnectorField("Create-My-User","OBP uses the tags to group the endpoints, the relevant endpoints can share the same tag. ")
|
||||
glossaryItems += makeGlossaryItem("tags", tagsExample)
|
||||
|
||||
lazy val perHourExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
@ -1417,7 +1497,7 @@ object ExampleValue {
|
||||
lazy val otherAccountSecondaryRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("other_account_secondary_routing_scheme", otherAccountSecondaryRoutingSchemeExample)
|
||||
|
||||
lazy val processExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
lazy val processExample = ConnectorField("obp.getBank","The format must be obp.xxxx, 'obp.' is the prefix, xxx will be the connector method name")
|
||||
glossaryItems += makeGlossaryItem("process", processExample)
|
||||
|
||||
lazy val otherBranchRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
@ -1624,7 +1704,7 @@ object ExampleValue {
|
||||
lazy val actualDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("actual_date", actualDateExample)
|
||||
|
||||
lazy val exampleOutboundMessageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
lazy val exampleOutboundMessageExample = ConnectorField("{}","this will the json object")
|
||||
glossaryItems += makeGlossaryItem("example_outbound_message", exampleOutboundMessageExample)
|
||||
|
||||
lazy val canDeleteWhereTagExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
@ -1918,6 +1998,9 @@ object ExampleValue {
|
||||
lazy val descriptionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("description", descriptionExample)
|
||||
|
||||
lazy val dynamicResourceDocdescriptionExample = ConnectorField("Create one User", "the description for this endpoint")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.description", dynamicResourceDocdescriptionExample)
|
||||
|
||||
lazy val canDeleteCommentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("can_delete_comment", canDeleteCommentExample)
|
||||
|
||||
@ -1948,6 +2031,8 @@ object ExampleValue {
|
||||
lazy val summaryExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("summary", summaryExample)
|
||||
|
||||
lazy val dynamicResourceDocSummaryExample = ConnectorField("Create My User","The summary of this endpoint")
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.summary", dynamicResourceDocSummaryExample)
|
||||
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
@ -14,7 +14,6 @@ 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.{DynamicEndpointHelper, DynamicEntityInfo}
|
||||
import code.authtypevalidation.{AuthenticationTypeValidationProvider, JsonAuthTypeValidation}
|
||||
import code.bankconnectors.Connector
|
||||
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
|
||||
@ -60,7 +59,10 @@ import code.validation.{JsonSchemaValidationProvider, JsonValidation}
|
||||
import net.liftweb.http.JsonResponse
|
||||
import net.liftweb.util.Props
|
||||
import code.api.JsonResponseException
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityInfo}
|
||||
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
|
||||
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
|
||||
import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc}
|
||||
|
||||
object NewStyle {
|
||||
lazy val endpoints: List[(String, String)] = List(
|
||||
@ -225,7 +227,7 @@ object NewStyle {
|
||||
unboxFullOrFail(_, callContext, s"$BankNotFound Current BankId is $bankId", 404)
|
||||
}
|
||||
}
|
||||
def getBanks(callContext: Option[CallContext]) : OBPReturnType[List[Bank]] = {
|
||||
def getBanks(callContext: Option[CallContext]) : Future[(List[Bank], Option[CallContext])] = {
|
||||
Connector.connector.vend.getBanks(callContext: Option[CallContext]) map {
|
||||
connectorEmptyResponse(_, callContext)
|
||||
}
|
||||
@ -2351,7 +2353,7 @@ object NewStyle {
|
||||
}
|
||||
|
||||
private[this] val dynamicEntityTTL = {
|
||||
if(Props.testMode) 0
|
||||
if(Props.testMode || Props.devMode) 0
|
||||
else APIUtil.getPropsValue(s"dynamicEntity.cache.ttl.seconds", "30").toInt
|
||||
}
|
||||
|
||||
@ -2846,13 +2848,13 @@ object NewStyle {
|
||||
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonConnectorMethodExists(connectorMethodId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def connectorMethodExists(connectorMethodId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = ConnectorMethodProvider.provider.vend.getById(connectorMethodId)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def isJsonConnectorMethodNameExists(connectorMethodName: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def connectorMethodNameExists(connectorMethodName: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = ConnectorMethodProvider.provider.vend.getByMethodNameWithoutCache(connectorMethodName)
|
||||
(result.isDefined, callContext)
|
||||
@ -2870,5 +2872,87 @@ object NewStyle {
|
||||
(unboxFullOrFail(connectorMethod, callContext, s"$ConnectorMethodNotFound Current CONNECTOR_METHOD_ID(${connectorMethodId})", 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicResourceDocExists(requestVerb : String, requestUrl : String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicResourceDocProvider.provider.vend.getByVerbAndUrl(requestVerb, requestUrl)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def createJsonDynamicResourceDoc(dynamicResourceDoc: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val newInternalConnector = DynamicResourceDocProvider.provider.vend.create(dynamicResourceDoc)
|
||||
val errorMsg = s"$UnknownError Can not create Dynamic Resource Doc in the backend. "
|
||||
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def updateJsonDynamicResourceDoc(entity: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val updatedConnectorMethod = DynamicResourceDocProvider.provider.vend.update(entity: JsonDynamicResourceDoc)
|
||||
val errorMsg = s"$UnknownError Can not update Dynamic Resource Doc in the backend. "
|
||||
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicResourceDocExists(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def getJsonDynamicResourceDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicResourceDoc]] =
|
||||
Future {
|
||||
val dynamicResourceDocs: List[JsonDynamicResourceDoc] = DynamicResourceDocProvider.provider.vend.getAll()
|
||||
dynamicResourceDocs -> callContext
|
||||
}
|
||||
|
||||
def getJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
|
||||
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def deleteJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.deleteById(dynamicResourceDocId)
|
||||
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocDeleteError Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def createJsonDynamicMessageDoc(dynamicMessageDoc: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val newInternalConnector = DynamicMessageDocProvider.provider.vend.create(dynamicMessageDoc)
|
||||
val errorMsg = s"$UnknownError Can not create Dynamic Message Doc in the backend. "
|
||||
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def updateJsonDynamicMessageDoc(entity: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val updatedConnectorMethod = DynamicMessageDocProvider.provider.vend.update(entity: JsonDynamicMessageDoc)
|
||||
val errorMsg = s"$UnknownError Can not update Dynamic Message Doc in the backend. "
|
||||
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicMessageDocExists(process: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicMessageDocProvider.provider.vend.getByProcess(process)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def getJsonDynamicMessageDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicMessageDoc]] =
|
||||
Future {
|
||||
val dynamicMessageDocs: List[JsonDynamicMessageDoc] = DynamicMessageDocProvider.provider.vend.getAll()
|
||||
dynamicMessageDocs -> callContext
|
||||
}
|
||||
|
||||
def getJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.getById(dynamicMessageDocId)
|
||||
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicMessageDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def deleteJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.deleteById(dynamicMessageDocId)
|
||||
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocDeleteError", 400), callContext)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ package code.api.v4_0_0
|
||||
import code.DynamicData.DynamicData
|
||||
import code.DynamicEndpoint.DynamicEndpointSwagger
|
||||
import code.accountattribute.AccountAttributeX
|
||||
import code.api.ChargePolicy
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{logoutLinkV400, _}
|
||||
import code.api.{ChargePolicy, JsonResponseException}
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, logoutLinkV400, _}
|
||||
import code.api.util.APIUtil.{fullBoxOrException, _}
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ApiTag._
|
||||
@ -23,12 +23,14 @@ import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200}
|
||||
import code.api.v2_1_0._
|
||||
import code.api.v3_0_0.JSONFactory300
|
||||
import code.api.v3_1_0._
|
||||
import code.api.v4_0_0.DynamicEndpointHelper.DynamicReq
|
||||
import code.api.v4_0_0.dynamic.DynamicEndpointHelper.DynamicReq
|
||||
import code.api.v4_0_0.JSONFactory400.{createBalancesJson, createBankAccountJSON, createCallsLimitJson, createNewCoreBankAccountJson}
|
||||
import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
|
||||
import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo, EntityName, MockResponseHolder}
|
||||
import code.apicollection.MappedApiCollectionsProvider
|
||||
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
|
||||
import code.authtypevalidation.JsonAuthTypeValidation
|
||||
import code.bankconnectors.{Connector, InternalConnector}
|
||||
import code.bankconnectors.{Connector, DynamicConnector, InternalConnector}
|
||||
import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody}
|
||||
import code.consent.{ConsentStatus, Consents}
|
||||
import code.dynamicEntity.{DynamicEntityCommons, ReferenceType}
|
||||
@ -70,6 +72,12 @@ import org.apache.commons.collections4.CollectionUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.util.Date
|
||||
import code.dynamicMessageDoc.JsonDynamicMessageDoc
|
||||
import code.dynamicResourceDoc.JsonDynamicResourceDoc
|
||||
|
||||
import java.net.URLEncoder
|
||||
import code.api.v4_0_0.dynamic.practise.DynamicEndpointCodeGenerator
|
||||
|
||||
import scala.collection.immutable.{List, Nil}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
@ -4303,7 +4311,7 @@ trait APIMethods400 {
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle),
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle),
|
||||
Some(List(canCreateDynamicEndpoint)))
|
||||
|
||||
lazy val createDynamicEndpoint: OBPEndpoint = {
|
||||
@ -4354,7 +4362,7 @@ trait APIMethods400 {
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle),
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle),
|
||||
Some(List(canGetDynamicEndpoint)))
|
||||
|
||||
lazy val getDynamicEndpoint: OBPEndpoint = {
|
||||
@ -4393,7 +4401,7 @@ trait APIMethods400 {
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle),
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle),
|
||||
Some(List(canGetDynamicEndpoints)))
|
||||
|
||||
lazy val getDynamicEndpoints: OBPEndpoint = {
|
||||
@ -4426,7 +4434,7 @@ trait APIMethods400 {
|
||||
DynamicEndpointNotFoundByDynamicEndpointId,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle),
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle),
|
||||
Some(List(canDeleteDynamicEndpoint)))
|
||||
|
||||
lazy val deleteDynamicEndpoint : OBPEndpoint = {
|
||||
@ -4458,7 +4466,7 @@ trait APIMethods400 {
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle)
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle)
|
||||
)
|
||||
|
||||
lazy val getMyDynamicEndpoints: OBPEndpoint = {
|
||||
@ -4491,7 +4499,7 @@ trait APIMethods400 {
|
||||
DynamicEndpointNotFoundByDynamicEndpointId,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle),
|
||||
List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle),
|
||||
)
|
||||
|
||||
lazy val deleteMyDynamicEndpoint : OBPEndpoint = {
|
||||
@ -6944,7 +6952,7 @@ trait APIMethods400 {
|
||||
|
|
||||
|The method_body is URL-encoded format String
|
||||
|""",
|
||||
jsonConnectorMethod.copy(internalConnectorId=None),
|
||||
jsonConnectorMethod.copy(connectorMethodId=None),
|
||||
jsonConnectorMethod,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
@ -6963,7 +6971,7 @@ trait APIMethods400 {
|
||||
json.extract[JsonConnectorMethod]
|
||||
}
|
||||
|
||||
(isExists, callContext) <- NewStyle.function.isJsonConnectorMethodNameExists(jsonConnectorMethod.methodName, Some(cc))
|
||||
(isExists, callContext) <- NewStyle.function.connectorMethodNameExists(jsonConnectorMethod.methodName, Some(cc))
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$ConnectorMethodAlreadyExists Please use a different method_name(${jsonConnectorMethod.methodName})") {
|
||||
(!isExists)
|
||||
}
|
||||
@ -7086,6 +7094,451 @@ trait APIMethods400 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(createDynamicResourceDoc),
|
||||
"POST",
|
||||
"/management/dynamic-resource-docs",
|
||||
"Create Dynamic Resource Doc",
|
||||
s"""Create a Dynamic Resource Doc.
|
||||
|
|
||||
|The connector_method_body is URL-encoded format String
|
||||
|""",
|
||||
jsonDynamicResourceDoc.copy(dynamicResourceDocId=None),
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canCreateDynamicResourceDoc)))
|
||||
|
||||
lazy val createDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
jsonDynamicResourceDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicResourceDoc]
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""") {
|
||||
Set("POST", "PUT", "GET", "DELETE").contains(jsonDynamicResourceDoc.requestVerb)
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String "" or just totally omit the field""") {
|
||||
(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.exampleRequestBody) match {
|
||||
case ("GET" | "DELETE", Some(JString(s))) => //we support the empty string "" here
|
||||
StringUtils.isBlank(s)
|
||||
case ("GET" | "DELETE", Some(requestBody)) => //we add the guard, we forbid any json objects in GET/DELETE request body.
|
||||
requestBody == JNothing
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicResourceDocAlreadyExists The combination of request_url(${jsonDynamicResourceDoc.requestUrl}) and request_verb(${jsonDynamicResourceDoc.requestVerb}) must be unique") {
|
||||
(!isExists)
|
||||
}
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(jsonDynamicResourceDoc, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateDynamicResourceDoc),
|
||||
"PUT",
|
||||
"/management/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Update Dynamic Resource Doc",
|
||||
s"""Update a Dynamic Resource Doc.
|
||||
|
|
||||
|The connector_method_body is URL-encoded format String
|
||||
|""",
|
||||
jsonDynamicResourceDoc.copy(dynamicResourceDocId = None),
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canUpdateDynamicResourceDoc)))
|
||||
|
||||
lazy val updateDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonPut json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicResourceDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicResourceDoc]
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""") {
|
||||
Set("POST", "PUT", "GET", "DELETE").contains(dynamicResourceDocBody.requestVerb)
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String""") {
|
||||
(dynamicResourceDocBody.requestVerb, dynamicResourceDocBody.exampleRequestBody) match {
|
||||
case ("GET" | "DELETE", Some(JString(s))) =>
|
||||
StringUtils.isBlank(s)
|
||||
case ("GET" | "DELETE", Some(requestBody)) =>
|
||||
requestBody == JNothing
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteDynamicResourceDoc),
|
||||
"DELETE",
|
||||
"/management/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Delete Dynamic Resource Doc",
|
||||
s"""Delete a Dynamic Resource Doc.
|
||||
|""",
|
||||
EmptyBody,
|
||||
BooleanBody(true),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canDeleteDynamicResourceDoc)))
|
||||
|
||||
lazy val deleteDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(dynamicResourceDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(getDynamicResourceDoc),
|
||||
"GET",
|
||||
"/management/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Get Dynamic Resource Doc by Id",
|
||||
s"""Get a Dynamic Resource Doc by DYNAMIC-RESOURCE-DOC-ID.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canGetDynamicResourceDoc)))
|
||||
|
||||
lazy val getDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAllDynamicResourceDocs,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAllDynamicResourceDocs),
|
||||
"GET",
|
||||
"/management/dynamic-resource-docs",
|
||||
"Get all Dynamic Resource Docs",
|
||||
s"""Get all Dynamic Resource Docs.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
ListResult("dynamic-resource-docs", jsonDynamicResourceDoc::Nil),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canGetAllDynamicResourceDocs)))
|
||||
|
||||
lazy val getAllDynamicResourceDocs: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-resource-docs", dynamicResourceDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
buildDynamicEndpointTemplate,
|
||||
implementedInApiVersion,
|
||||
nameOf(buildDynamicEndpointTemplate),
|
||||
"POST",
|
||||
"/management/dynamic-resource-docs/endpoint-code",
|
||||
"Create Dynamic Resource Doc endpoint code",
|
||||
s"""Create a Dynamic Resource Doc endpoint code.
|
||||
|
|
||||
|copy the response and past to ${nameOf(PractiseEndpoint)}, So you can have the benefits of
|
||||
|auto compilation and debug
|
||||
|""",
|
||||
jsonResourceDocFragment,
|
||||
jsonCodeTemplate,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
None)
|
||||
|
||||
lazy val buildDynamicEndpointTemplate: OBPEndpoint = {
|
||||
case "management" :: "dynamic-resource-docs" :: "endpoint-code" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
resourceDocFragment <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $ResourceDocFragment", 400, cc.callContext) {
|
||||
json.extract[ResourceDocFragment]
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""") {
|
||||
Set("POST", "PUT", "GET", "DELETE").contains(resourceDocFragment.requestVerb)
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String""") {
|
||||
(resourceDocFragment.requestVerb, resourceDocFragment.exampleRequestBody) match {
|
||||
case ("GET" | "DELETE", Some(JString(s))) =>
|
||||
StringUtils.isBlank(s)
|
||||
case ("GET" | "DELETE", Some(requestBody)) =>
|
||||
requestBody == JNothing
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
code = DynamicEndpointCodeGenerator.buildTemplate(resourceDocFragment)
|
||||
|
||||
} yield {
|
||||
("code" -> URLEncoder.encode(code, "UTF-8"), HttpCode.`201`(cc.callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(createDynamicMessageDoc),
|
||||
"POST",
|
||||
"/management/dynamic-message-docs",
|
||||
"Create Dynamic Message Doc",
|
||||
s"""Create a Dynamic Message Doc.
|
||||
|""",
|
||||
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canCreateDynamicMessageDoc)))
|
||||
|
||||
lazy val createDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-message-docs" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicMessageDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicMessageDoc]
|
||||
}
|
||||
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(dynamicMessageDoc.process, cc.callContext)
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicMessageDocAlreadyExists The json body process(${dynamicMessageDoc.process}) already exists") {
|
||||
(!dynamicMessageDocExisted)
|
||||
}
|
||||
connectorMethod = DynamicConnector.createFunction(dynamicMessageDoc.process, dynamicMessageDoc.decodedMethodBody)
|
||||
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(dynamicMessageDoc, callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateDynamicMessageDoc),
|
||||
"PUT",
|
||||
"/management/dynamic-message-docs/DYNAMIC-MESSAGE-DOC-ID",
|
||||
"Update Dynamic Message Doc",
|
||||
s"""Update a Dynamic Message Doc.
|
||||
|""",
|
||||
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canUpdateDynamicMessageDoc)))
|
||||
|
||||
lazy val updateDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonPut json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicMessageDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicMessageDoc]
|
||||
}
|
||||
connectorMethod = DynamicConnector.createFunction(dynamicMessageDocBody.process, dynamicMessageDocBody.decodedMethodBody)
|
||||
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(getDynamicMessageDoc),
|
||||
"GET",
|
||||
"/management/dynamic-message-docs/DYNAMIC-MESSAGE-DOC-ID",
|
||||
"Get Dynamic Message Doc by Id",
|
||||
s"""Get a Dynamic Message Doc by DYNAMIC-MESSAGE-DOC-ID.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canGetDynamicMessageDoc)))
|
||||
|
||||
lazy val getDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAllDynamicMessageDocs,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAllDynamicMessageDocs),
|
||||
"GET",
|
||||
"/management/dynamic-message-docs",
|
||||
"Get all Dynamic Message Docs",
|
||||
s"""Get all Dynamic Message Docs.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
ListResult("dynamic-message-docs", jsonDynamicMessageDoc::Nil),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canGetAllDynamicMessageDocs)))
|
||||
|
||||
lazy val getAllDynamicMessageDocs: OBPEndpoint = {
|
||||
case "management" :: "dynamic-message-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-message-docs", dynamicMessageDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteDynamicMessageDoc),
|
||||
"DELETE",
|
||||
"/management/dynamic-message-docs/DYNAMIC-MESSAGE-DOC-ID",
|
||||
"Delete Dynamic Message Doc",
|
||||
s"""Delete a Dynamic Message Doc.
|
||||
|""",
|
||||
EmptyBody,
|
||||
BooleanBody(true),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canDeleteDynamicMessageDoc)))
|
||||
|
||||
lazy val deleteDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(dynamicMessageDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.api.attributedefinition.AttributeDefinition
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.APIUtil.{stringOptionOrNull, stringOrNull}
|
||||
@ -50,7 +49,9 @@ import code.standingorders.StandingOrderTrait
|
||||
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
|
||||
import code.userlocks.UserLocks
|
||||
import com.openbankproject.commons.model.{DirectDebitTrait, _}
|
||||
import com.openbankproject.commons.util.JsonAble
|
||||
import net.liftweb.common.{Box, Full}
|
||||
import net.liftweb.json.{Extraction, Formats, JValue, JsonAST}
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
@ -620,6 +621,13 @@ case class TransactionBankAccountJson(
|
||||
transaction_id: String
|
||||
)
|
||||
|
||||
case class ResourceDocFragment(
|
||||
requestVerb: String,
|
||||
requestUrl: String,
|
||||
exampleRequestBody: Option[JValue],
|
||||
successResponseBody: Option[JValue]
|
||||
) extends JsonFieldReName
|
||||
|
||||
object JSONFactory400 {
|
||||
|
||||
def createCallsLimitJson(rateLimiting: RateLimiting) : CallLimitJsonV400 = {
|
||||
|
||||
@ -38,6 +38,7 @@ import code.api.v3_0_0.APIMethods300
|
||||
import code.api.v3_0_0.custom.CustomAPIMethods300
|
||||
import code.api.v3_1_0.OBPAPI3_1_0.Implementations3_1_0
|
||||
import code.api.v3_1_0.{APIMethods310, OBPAPI3_1_0}
|
||||
import code.api.v4_0_0.dynamic.DynamicEndpoints
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
@ -86,6 +87,16 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
|
||||
|
||||
oauthServe(apiPrefix{Implementations4_0_0.genericEndpoint}, None)
|
||||
oauthServe(apiPrefix{Implementations4_0_0.dynamicEndpoint}, None)
|
||||
/**
|
||||
* Here is the place where we register the dynamicEndpoint, all the dynamic resource docs endpoints are here.
|
||||
* Actually, we only register one endpoint for all the dynamic resource docs endpoints.
|
||||
* For Liftweb, it just need to handle one endpoint,
|
||||
* all the router functionalities are in OBP code.
|
||||
* details: please also check code/api/v4_0_0/dynamic/DynamicEndpoints.findEndpoint method
|
||||
* NOTE: this must be the last one endpoint to register into Liftweb
|
||||
* Because firstly, Liftweb should look for the static endpoints --> then the dynamic ones.
|
||||
*/
|
||||
oauthServe(apiPrefix{DynamicEndpoints.dynamicEndpoint}, None)
|
||||
|
||||
logger.info(s"version $version has been run! There are ${routes.length} routes.")
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.APIUtil.{OBPEndpoint, OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture}
|
||||
import code.api.util.{CallContext, CustomJsonFormats}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
|
||||
/**
|
||||
* this is super trait of dynamic compile endpoint, the dynamic compiled code should extends this trait and supply
|
||||
* logic of process method
|
||||
*/
|
||||
trait DynamicCompileEndpoint {
|
||||
implicit val formats = CustomJsonFormats.formats
|
||||
|
||||
protected def process(callContext: CallContext, request: Req): Box[JsonResponse]
|
||||
|
||||
val endpoint: OBPEndpoint = new OBPEndpoint {
|
||||
override def isDefinedAt(x: Req): Boolean = true
|
||||
|
||||
override def apply(request: Req): CallContext => Box[JsonResponse] = process(_, request)
|
||||
}
|
||||
|
||||
protected implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
|
||||
futureToBoxedResponse(scalaFutureToLaFuture(scf))
|
||||
}
|
||||
protected def getPathParams(callContext: CallContext, request: Req): Map[String, String] = {
|
||||
val Some(resourceDoc) = callContext.resourceDocument
|
||||
resourceDoc.getPathParams(request.path.partPath)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,4 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.util
|
||||
import java.util.regex.Pattern
|
||||
import java.util.{Date, UUID}
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import akka.http.scaladsl.model.{HttpMethods, HttpMethod => AkkaHttpMethod}
|
||||
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
|
||||
@ -31,6 +25,11 @@ import org.apache.commons.collections4.MapUtils
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.util
|
||||
import java.util.regex.Pattern
|
||||
import java.util.{Date, UUID}
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.immutable.List
|
||||
import scala.collection.mutable
|
||||
@ -0,0 +1,191 @@
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, OBPEndpoint, PrimaryDataBody, ResourceDoc, StringBody}
|
||||
import code.api.util.{CallContext, DynamicUtil}
|
||||
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
import net.liftweb.json.{JNothing, JValue}
|
||||
import net.liftweb.json.JsonAST.{JBool, JDouble, JInt, JString}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.net.URLDecoder
|
||||
import scala.collection.immutable.List
|
||||
import scala.util.control.Breaks.{break, breakable}
|
||||
|
||||
object DynamicEndpoints {
|
||||
//TODO, better put all other dynamic endpoints into this list. eg: dynamicEntityEndpoints, dynamicSwaggerDocsEndpoints ....
|
||||
private val endpointGroups: List[EndpointGroup] = PractiseEndpointGroup :: DynamicResourceDocsEndpointGroup :: Nil
|
||||
|
||||
/**
|
||||
* this will find dynamic endpoint by request.
|
||||
* the dynamic endpoints can be in obp database or memory or generated by obp code.
|
||||
* This will be the OBP Router for all the dynamic endpoints.
|
||||
*
|
||||
*/
|
||||
private def findEndpoint(req: Req): Option[OBPEndpoint] = {
|
||||
var foundEndpoint: Option[OBPEndpoint] = None
|
||||
breakable{
|
||||
endpointGroups.foreach { endpointGroup => {
|
||||
val maybeEndpoint: Option[OBPEndpoint] = endpointGroup.endpoints.find(_.isDefinedAt(req))
|
||||
if(maybeEndpoint.isDefined) {
|
||||
foundEndpoint = maybeEndpoint
|
||||
break
|
||||
}
|
||||
}}
|
||||
}
|
||||
foundEndpoint
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint will be registered into Liftweb.
|
||||
* It is only one endpoint for Liftweb <---> but it mean many for obp dynamic endpoints
|
||||
* Because inside the method body, we override the `isDefinedAt` method,
|
||||
* We can loop all the dynamic endpoints from obp database (better check EndpointGroup.endpoints we generate the endpoints
|
||||
* by resourceDocs, then we can create the endpoints object in memory).
|
||||
*
|
||||
*/
|
||||
val dynamicEndpoint: OBPEndpoint = new OBPEndpoint {
|
||||
override def isDefinedAt(req: Req): Boolean = findEndpoint(req).isDefined
|
||||
|
||||
override def apply(req: Req): CallContext => Box[JsonResponse] = {
|
||||
val Some(endpoint) = findEndpoint(req)
|
||||
endpoint(req)
|
||||
}
|
||||
}
|
||||
|
||||
def dynamicResourceDocs: List[ResourceDoc] = endpointGroups.flatMap(_.docs)
|
||||
}
|
||||
|
||||
trait EndpointGroup {
|
||||
protected def resourceDocs: List[ResourceDoc]
|
||||
|
||||
protected lazy val urlPrefix: String = ""
|
||||
|
||||
// reset urlPrefix resourceDocs
|
||||
def docs: List[ResourceDoc] = if(StringUtils.isBlank(urlPrefix)) {
|
||||
resourceDocs
|
||||
} else {
|
||||
resourceDocs map { doc =>
|
||||
val newUrl = s"/$urlPrefix/${doc.requestUrl}".replace("//", "/")
|
||||
val newDoc = doc.copy(requestUrl = newUrl)
|
||||
newDoc.connectorMethods = doc.connectorMethods // copy method will not keep var value, So here reset it manually
|
||||
newDoc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will generate the endpoints from the resourceDocs.
|
||||
*/
|
||||
def endpoints: List[OBPEndpoint] = docs.map(wrapEndpoint)
|
||||
|
||||
//fill callContext with resourceDoc and operationId
|
||||
private def wrapEndpoint(resourceDoc: ResourceDoc): OBPEndpoint = {
|
||||
|
||||
val endpointFunction = resourceDoc.wrappedWithAuthCheck(resourceDoc.partialFunction)
|
||||
|
||||
new OBPEndpoint {
|
||||
override def isDefinedAt(req: Req): Boolean = req.requestType.method == resourceDoc.requestVerb && endpointFunction.isDefinedAt(req)
|
||||
|
||||
override def apply(req: Req): CallContext => Box[JsonResponse] = {
|
||||
(callContext: CallContext) => {
|
||||
// fill callContext with resourceDoc and operationId, this will map the resourceDoc to endpoint.
|
||||
val newCallContext = callContext.copy(resourceDocument = Some(resourceDoc), operationId = Some(resourceDoc.operationId))
|
||||
endpointFunction(req)(newCallContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class will generate the ResourceDoc class fields(requestBody: Product, successResponse: Product and partialFunction: OBPEndpoint)
|
||||
* by parameters: JValues and Strings.
|
||||
* successResponseBody: Option[JValue] --> toCaseObject(from JValue --> Scala code --> DynamicUtil.compileScalaCode --> generate the object.
|
||||
* methodBody: String --> prepare the template api level scala code --> DynamicUtil.compileScalaCode --> generate the api level code.
|
||||
*
|
||||
* @param exampleRequestBody exampleRequestBody from the post json body, it is JValue here.
|
||||
* @param successResponseBody successResponseBody from the post json body,it is JValue here.
|
||||
* @param methodBody it is url-encoded string for the api level code.
|
||||
*/
|
||||
case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBody: Option[JValue], methodBody: String) {
|
||||
val decodedMethodBody = URLDecoder.decode(methodBody, "UTF-8")
|
||||
val requestBody: Product = exampleRequestBody match {
|
||||
//this case means, we accept the empty string "" from json post body, we need to map it to None.
|
||||
case Some(JString(s)) if StringUtils.isBlank(s) => toCaseObject(None)
|
||||
// Here we will generate the object by the JValue (exampleRequestBody)
|
||||
case _ => toCaseObject(exampleRequestBody)
|
||||
}
|
||||
val successResponse: Product = toCaseObject(successResponseBody)
|
||||
|
||||
val partialFunction: OBPEndpoint = {
|
||||
|
||||
//If the requestBody is PrimaryDataBody, return None. otherwise, return the exampleRequestBody:Option[JValue]
|
||||
// In side OBP resourceDoc, requestBody and successResponse must be Product type,
|
||||
// both can not be the primitive type: `boolean, string, kong, int, long, double` and List.
|
||||
// PrimaryDataBody is used for OBP mapping these types.
|
||||
// Note: List and object will generate the `Case class`, `case class` must not be PrimaryDataBody. only these two
|
||||
// possibilities: case class or PrimaryDataBody
|
||||
val requestExample: Option[JValue] = if (requestBody.isInstanceOf[PrimaryDataBody[_]]) {
|
||||
None
|
||||
} else exampleRequestBody
|
||||
|
||||
val responseExample: Option[JValue] = if (successResponse.isInstanceOf[PrimaryDataBody[_]]) {
|
||||
None
|
||||
} else successResponseBody
|
||||
|
||||
// buildCaseClasses --> will generate the following case classes string, which are used for the scala template code.
|
||||
// case class RequestRootJsonClass(name: String, age: Long)
|
||||
// case class ResponseRootJsonClass(person_id: String, name: String, age: Long)
|
||||
val (requestBodyCaseClasses, responseBodyCaseClasses) = DynamicEndpointCodeGenerator.buildCaseClasses(requestExample, responseExample)
|
||||
|
||||
val code =
|
||||
s"""
|
||||
|import code.api.util.APIUtil.errorJsonResponse
|
||||
|import code.api.util.CallContext
|
||||
|import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|
||||
|import code.api.util.NewStyle.HttpCode
|
||||
|import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
|
||||
|import net.liftweb.common.{Box, EmptyBox, Full}
|
||||
|import net.liftweb.http.{JsonResponse, Req}
|
||||
|import net.liftweb.json.MappingException
|
||||
|
|
||||
|import scala.concurrent.Future
|
||||
|
|
||||
|$requestBodyCaseClasses
|
||||
|
|
||||
|$responseBodyCaseClasses
|
||||
|
|
||||
|(new DynamicCompileEndpoint {
|
||||
| override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
|
||||
| $decodedMethodBody
|
||||
| }
|
||||
|}).endpoint
|
||||
|
|
||||
|""".stripMargin
|
||||
val endpointMethod = DynamicUtil.compileScalaCode[OBPEndpoint](code)
|
||||
|
||||
endpointMethod match {
|
||||
case Full(func) => func
|
||||
case Failure(msg: String, exception: Box[Throwable], _) =>
|
||||
throw exception.getOrElse(new RuntimeException(msg))
|
||||
case _ => throw new RuntimeException("compiled code return nothing")
|
||||
}
|
||||
}
|
||||
|
||||
private def toCaseObject(jValue: Option[JValue]): Product = {
|
||||
if (jValue.isEmpty || jValue.exists(JNothing ==)) {
|
||||
EmptyBody
|
||||
} else {
|
||||
jValue.orNull match {
|
||||
case JBool(b) => BooleanBody(b)
|
||||
case JInt(l) => LongBody(l.toLong)
|
||||
case JDouble(d) => DoubleBody(d)
|
||||
case JString(s) => StringBody(s)
|
||||
case v => DynamicUtil.toCaseObject(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package code.api.v4_0_0
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.APIUtil.{EmptyBody, ResourceDoc, authenticationRequiredMessage, generateUUID}
|
||||
import code.api.util.ApiRole.getOrCreateDynamicApiRole
|
||||
import code.api.util.ApiTag.{ResourceDocTag, apiTagApi, apiTagDynamic, apiTagDynamicEndpoint, apiTagNewStyle}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
|
||||
import code.api.util.{APIUtil, ApiRole, ApiTag, ExampleValue, NewStyle}
|
||||
import code.api.util._
|
||||
import com.openbankproject.commons.model.enums.{DynamicEntityFieldType, DynamicEntityOperation}
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
import net.liftweb.json.JsonDSL._
|
||||
@ -0,0 +1,60 @@
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.APIUtil.ResourceDoc
|
||||
import code.api.util.{APIUtil, ApiRole, ApiTag}
|
||||
import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc}
|
||||
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object DynamicResourceDocsEndpointGroup extends EndpointGroup {
|
||||
override lazy val urlPrefix: String = APIUtil.getPropsValue("url.prefix.dynamic.resourceDoc", "dynamic-resource-doc")
|
||||
|
||||
|
||||
override protected def resourceDocs: List[APIUtil.ResourceDoc] =
|
||||
DynamicResourceDocProvider.provider.vend.getAllAndConvert(toResourceDoc)
|
||||
|
||||
private val apiVersion : ScannedApiVersion = ApiVersion.v4_0_0
|
||||
|
||||
/**
|
||||
* this is a function, convert JsonDynamicResourceDoc => ResourceDoc
|
||||
*
|
||||
* the core difference between JsonDynamicResourceDoc and ResourceDoc are the following:
|
||||
*
|
||||
* 1st: JsonDynamicResourceDoc.methodBody <---vs---> ResourceDoc no methodBody
|
||||
*
|
||||
* 2rd: JsonDynamicResourceDoc.exampleRequestBody : Option[JValue] <---vs---> ResourceDoc.exampleRequestBody: scala.Product
|
||||
*
|
||||
* 3rd: JsonDynamicResourceDoc no partialFunction <---vs---> partialFunction: OBPEndpoint
|
||||
*
|
||||
* ....
|
||||
*
|
||||
* We need to prepare the ResourceDoc fields from JsonDynamicResourceDoc.
|
||||
* @CompiledObjects also see this class,
|
||||
*
|
||||
*/
|
||||
private val toResourceDoc: JsonDynamicResourceDoc => ResourceDoc = { dynamicDoc =>
|
||||
val compiledObjects = CompiledObjects(dynamicDoc.exampleRequestBody, dynamicDoc.successResponseBody, dynamicDoc.methodBody)
|
||||
ResourceDoc(
|
||||
partialFunction = compiledObjects.partialFunction, //connectorMethodBody
|
||||
implementedInApiVersion = apiVersion,
|
||||
partialFunctionName = dynamicDoc.partialFunctionName + "_" + (dynamicDoc.requestVerb + dynamicDoc.requestUrl).hashCode,
|
||||
requestVerb = dynamicDoc.requestVerb,
|
||||
requestUrl = dynamicDoc.requestUrl,
|
||||
summary = dynamicDoc.summary,
|
||||
description = dynamicDoc.description,
|
||||
exampleRequestBody = compiledObjects.requestBody,// compiled case object
|
||||
successResponseBody = compiledObjects.successResponse, //compiled case object
|
||||
errorResponseBodies = StringUtils.split(dynamicDoc.errorResponseBodies,",").toList,
|
||||
tags = dynamicDoc.tags.split(",").map(ApiTag(_)).toList,
|
||||
roles = Option(dynamicDoc.roles)
|
||||
.filter(StringUtils.isNoneBlank(_))
|
||||
.map { it =>
|
||||
StringUtils.split(it, ",")
|
||||
.map(ApiRole.getOrCreateDynamicApiRole(_))
|
||||
.toList
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
package code.api.v4_0_0.dynamic.practise
|
||||
|
||||
import code.api.util.APIUtil.ResourceDoc
|
||||
import code.api.v4_0_0.ResourceDocFragment
|
||||
import com.google.common.base.CaseFormat
|
||||
import com.openbankproject.commons.util.JsonUtils
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.JsonAST.{JBool, JDouble, JInt, JString}
|
||||
import net.liftweb.json.{JArray, JObject, JValue}
|
||||
import org.apache.commons.lang3.{ArrayUtils, StringUtils}
|
||||
|
||||
object DynamicEndpointCodeGenerator {
|
||||
|
||||
def buildTemplate(fragment: ResourceDocFragment) = {
|
||||
val pathParamNames = ResourceDoc.findPathVariableNames(fragment.requestUrl)
|
||||
|
||||
val pathVariables = if(ArrayUtils.isNotEmpty(pathParamNames)) {
|
||||
val variables = pathParamNames.map(it => s"""val ${CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, it)} = pathParams("$it")""")
|
||||
.mkString("\n ")
|
||||
s""" // get Path Parameters, example:
|
||||
| // if the requestUrl of resourceDoc is /hello/banks/BANK_ID/world
|
||||
| // the request path is /hello/banks/bank_x/world
|
||||
| //pathParams.get("BANK_ID") will get Option("bank_x") value
|
||||
| val pathParams = getPathParams(callContext, request)
|
||||
| $variables
|
||||
|""".stripMargin
|
||||
} else ""
|
||||
|
||||
val (requestBodyCaseClasses, responseBodyCaseClasses) = buildCaseClasses(fragment.exampleRequestBody, fragment.successResponseBody)
|
||||
|
||||
def requestEntityExp(str:String) =
|
||||
s""" val requestEntity = request.json match {
|
||||
| case Full(zson) =>
|
||||
| try {
|
||||
| zson.extract[$str]
|
||||
| } catch {
|
||||
| case e: MappingException =>
|
||||
| return Full(errorJsonResponse(s"$$InvalidJsonFormat $${e.msg}"))
|
||||
| }
|
||||
| case _: EmptyBox =>
|
||||
| return Full(errorJsonResponse(s"$$InvalidRequestPayload Current request has no payload"))
|
||||
| }
|
||||
|""".stripMargin
|
||||
|
||||
val requestEntity = fragment.exampleRequestBody match {
|
||||
case Some(JBool(_)) => requestEntityExp("Boolean")
|
||||
case Some(JInt(_)) => requestEntityExp("Long")
|
||||
case Some(JDouble(_)) => requestEntityExp("Double")
|
||||
case Some(JString(s)) if StringUtils.isNotBlank(s) => requestEntityExp("String")
|
||||
case Some(JObject(_)) | Some(JArray(_)) => requestEntityExp("RequestRootJsonClass")
|
||||
case _ => ""
|
||||
}
|
||||
|
||||
val responseEntity = fragment.successResponseBody match {
|
||||
case Some(JBool(_)) => "val responseBody: Boolean = null"
|
||||
case Some(JInt(_)) => "val responseBody: Long = null"
|
||||
case Some(JDouble(_)) => "val responseBody: Double = null"
|
||||
case Some(JString(_)) => "val responseBody: String = null"
|
||||
case Some(JObject(_)) | Some(JArray(_)) => "val responseBody:ResponseRootJsonClass = null"
|
||||
case _ => "val responseBody = null"
|
||||
}
|
||||
|
||||
s"""
|
||||
|$requestBodyCaseClasses
|
||||
|
|
||||
|$responseBodyCaseClasses
|
||||
|
|
||||
| // request method
|
||||
| val requestMethod = "${fragment.requestVerb}"
|
||||
| val requestUrl = "${fragment.requestUrl}"
|
||||
|
|
||||
| // copy the whole method body as "dynamicResourceDoc" method body
|
||||
| override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
|
||||
| // please add import sentences here, those used by this method
|
||||
|
|
||||
| val Some(resourceDoc) = callContext.resourceDocument
|
||||
| val hasRequestBody = request.body.isDefined
|
||||
|
|
||||
|$pathVariables
|
||||
|
|
||||
|$requestEntity
|
||||
|
|
||||
| // please add business logic here
|
||||
| $responseEntity
|
||||
| Future.successful {
|
||||
| (responseBody, HttpCode.`200`(callContext.callContext))
|
||||
| }
|
||||
| }
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
def buildTemplate(requestVerb: String,
|
||||
requestUrl: String,
|
||||
exampleRequestBody: Option[String],
|
||||
successResponseBody: Option[String]): String = {
|
||||
|
||||
buildTemplate(
|
||||
ResourceDocFragment(requestVerb, requestUrl,
|
||||
exampleRequestBody.map(json.parse(_)),
|
||||
successResponseBody.map(json.parse(_))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* also see @com.openbankproject.commons.util.JsonUtils#toCaseClasses
|
||||
* it will generate the following case class strings:
|
||||
*
|
||||
* // all request case classes
|
||||
* // case class RequestRootJsonClass(name: String, age: Long)
|
||||
* // all response case classes
|
||||
* // case class ResponseRootJsonClass(person_id: String, name: String, age: Long)
|
||||
*
|
||||
* @param exampleRequestBody : Option[JValue]
|
||||
* @param successResponseBody: Option[JValue]
|
||||
* @return
|
||||
*/
|
||||
def buildCaseClasses(exampleRequestBody: Option[JValue], successResponseBody: Option[JValue]): (String, String) = {
|
||||
val requestBodyCaseClasses = if(exampleRequestBody.exists(it => it.isInstanceOf[JObject] || it.isInstanceOf[JArray])) {
|
||||
val Some(requestBody) = exampleRequestBody
|
||||
s""" // all request case classes
|
||||
| ${JsonUtils.toCaseClasses(requestBody, "Request")}
|
||||
|""".stripMargin
|
||||
} else ""
|
||||
|
||||
val responseBodyCaseClasses = if(successResponseBody.exists(it => it.isInstanceOf[JObject] || it.isInstanceOf[JArray])) {
|
||||
val Some(responseBody) = successResponseBody
|
||||
s""" // all response case classes
|
||||
| ${JsonUtils.toCaseClasses(responseBody, "Response")}
|
||||
|""".stripMargin
|
||||
} else ""
|
||||
|
||||
(requestBodyCaseClasses, responseBodyCaseClasses)
|
||||
}
|
||||
|
||||
/**
|
||||
* by call this main method, you can create dynamic resource doc method body
|
||||
* @param args
|
||||
*/
|
||||
def main(args: Array[String]): Unit = {
|
||||
|
||||
val requestVerb = "POST"
|
||||
val requestUrl = "/person/PERSON_ID"
|
||||
|
||||
val requestBody =
|
||||
"""
|
||||
|{
|
||||
| "name": "Jhon",
|
||||
| "age": 11
|
||||
|}
|
||||
|""".stripMargin
|
||||
val responseBoy =
|
||||
"""
|
||||
|{
|
||||
| "person_id": "person_id_value",
|
||||
| "name": "Jhon",
|
||||
| "age": 11
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val generatedCode = buildTemplate(requestVerb, requestUrl, Option(requestBody), Option(responseBoy))
|
||||
println(generatedCode)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package code.api.v4_0_0.dynamic.practise
|
||||
|
||||
import code.api.util.APIUtil.errorJsonResponse
|
||||
import code.api.util.CallContext
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
|
||||
import net.liftweb.common.{Box, EmptyBox, Full}
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
import net.liftweb.json.MappingException
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/**
|
||||
* practise new endpoint at this object, don't commit you practise code to git
|
||||
*/
|
||||
object PractiseEndpoint extends DynamicCompileEndpoint {
|
||||
// all request case classes
|
||||
case class RequestRootJsonClass(name: String, age: Long, hobby: Option[List[String]])
|
||||
|
||||
|
||||
// all response case classes
|
||||
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: Option[List[String]])
|
||||
|
||||
|
||||
// request method
|
||||
val requestMethod = "POST"
|
||||
val requestUrl = "/my_user/MY_USER_ID"
|
||||
|
||||
// copy the whole method body as "dynamicResourceDoc" method body
|
||||
override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
|
||||
// please add import sentences here, those used by this method
|
||||
|
||||
val Some(resourceDoc) = callContext.resourceDocument
|
||||
val hasRequestBody = request.body.isDefined
|
||||
|
||||
// get Path Parameters, example:
|
||||
// if the requestUrl of resourceDoc is /hello/banks/BANK_ID/world
|
||||
// the request path is /hello/banks/bank_x/world
|
||||
//pathParams.get("BANK_ID") will get Option("bank_x") value
|
||||
val pathParams = getPathParams(callContext, request)
|
||||
val myUserId = pathParams("MY_USER_ID")
|
||||
|
||||
|
||||
val requestEntity = request.json match {
|
||||
case Full(zson) =>
|
||||
try {
|
||||
zson.extract[RequestRootJsonClass]
|
||||
} catch {
|
||||
case e: MappingException =>
|
||||
return Full(errorJsonResponse(s"$InvalidJsonFormat ${e.msg}"))
|
||||
}
|
||||
case _: EmptyBox =>
|
||||
return Full(errorJsonResponse(s"$InvalidRequestPayload Current request has no payload"))
|
||||
}
|
||||
|
||||
|
||||
// please add business logic here
|
||||
val responseBody:ResponseRootJsonClass = ResponseRootJsonClass(s"${myUserId}_from_path", requestEntity.name, requestEntity.age, requestEntity.hobby)
|
||||
Future.successful {
|
||||
(responseBody, HttpCode.`200`(callContext.callContext))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package code.api.v4_0_0.dynamic.practise
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.APIUtil.{ResourceDoc, StringBody}
|
||||
import code.api.util.ApiTag.{apiTagDynamicResourceDoc, apiTagNewStyle}
|
||||
import code.api.util.ErrorMessages.UnknownError
|
||||
import code.api.v4_0_0.dynamic.EndpointGroup
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
/**
|
||||
* this is just for developer to create new dynamic endpoint, and debug it
|
||||
*/
|
||||
object PractiseEndpointGroup extends EndpointGroup{
|
||||
|
||||
override protected lazy val urlPrefix: String = "test-dynamic-resource-doc"
|
||||
|
||||
override protected def resourceDocs: List[APIUtil.ResourceDoc] = ResourceDoc(
|
||||
PractiseEndpoint.endpoint,
|
||||
ApiVersion.v4_0_0,
|
||||
"test-dynamic-resource-doc",
|
||||
PractiseEndpoint.requestMethod,
|
||||
PractiseEndpoint.requestUrl,
|
||||
"A test endpoint",
|
||||
s"""A test endpoint.
|
||||
|Just for debug method body of dynamic resource doc
|
||||
|""",
|
||||
StringBody("Any request body"),
|
||||
StringBody("Any response body"),
|
||||
List(
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle)) :: Nil
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package code.bankconnectors
|
||||
|
||||
import code.api.util.DynamicUtil.compileScalaCode
|
||||
import code.api.util.{CallContext, DynamicUtil}
|
||||
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
|
||||
import net.liftweb.common.Box
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType}
|
||||
|
||||
|
||||
object DynamicConnector {
|
||||
|
||||
|
||||
def invoke(process: String, args: Array[AnyRef], callContext: Option[CallContext]) = {
|
||||
val function: Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]] =
|
||||
getFunction(process).asInstanceOf[Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]]]
|
||||
function.map(f =>f(args: Array[AnyRef], callContext: Option[CallContext])).openOrThrowException(s"There is no process $process, it should not be called here")
|
||||
}
|
||||
|
||||
private def getFunction(process: String) = {
|
||||
DynamicMessageDocProvider.provider.vend.getByProcess(process) map {
|
||||
case v :JsonDynamicMessageDoc =>
|
||||
createFunction(process, v.decodedMethodBody).openOrThrowException(s"InternalConnector method compile fail")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* dynamic create function
|
||||
* @param process name of connector
|
||||
* @param methodBody method body of connector method
|
||||
* @return function of connector method that is dynamic created, can be Function0, Function1, Function2...
|
||||
*/
|
||||
def createFunction(process: String, methodBody:String): Box[Any] =
|
||||
{
|
||||
//messageDoc.process is a bit different with the methodName, we need tweak the format of it:
|
||||
//eg: process("obp.getBank") ==> methodName("getBank")
|
||||
val method = s"""
|
||||
|${DynamicUtil.importStatements}
|
||||
|def func(args: Array[AnyRef], callContext: Option[CallContext]): Future[Box[(AnyRef, Option[CallContext])]] = {
|
||||
| $methodBody
|
||||
|}
|
||||
|
|
||||
|func _
|
||||
|""".stripMargin
|
||||
compileScalaCode(method)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
package code.bankconnectors
|
||||
|
||||
import code.api.util.DynamicUtil.compileScalaCode
|
||||
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import net.liftweb.common.{Box, Failure}
|
||||
import net.sf.cglib.proxy.{Enhancer, MethodInterceptor, MethodProxy}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import code.api.util.DynamicUtil
|
||||
import scala.reflect.runtime.universe.{MethodSymbol, TermSymbol, typeOf}
|
||||
import scala.tools.reflect.{ToolBox, ToolBoxError}
|
||||
|
||||
object InternalConnector {
|
||||
|
||||
@ -26,10 +25,6 @@ object InternalConnector {
|
||||
// in this object, you must make sure this object is empty.
|
||||
}
|
||||
|
||||
// (methodName,methodBody) -> dynamic method function
|
||||
// connector methods count is 230, make the initialCapacity a little bigger
|
||||
private val dynamicMethods = new ConcurrentHashMap[(String, String), Any](300)
|
||||
|
||||
private val intercept:MethodInterceptor = (_: Any, method: Method, args: Array[AnyRef], _: MethodProxy) => {
|
||||
val methodName = method.getName
|
||||
if(methodName == nameOf(connector.callableMethods)) {
|
||||
@ -37,48 +32,18 @@ object InternalConnector {
|
||||
} else if (methodName.contains("$default$")) {
|
||||
method.invoke(connector, args:_*)
|
||||
} else {
|
||||
val result = getFunction(methodName).orNull match {
|
||||
case func: Function0[AnyRef] => func()
|
||||
case func: Function1[AnyRef,AnyRef] => func(args.head)
|
||||
case func: Function2[AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1))
|
||||
case func: Function3[AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2))
|
||||
case func: Function4[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3))
|
||||
case func: Function5[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4))
|
||||
case func: Function6[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5))
|
||||
case func: Function7[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6))
|
||||
case func: Function8[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7))
|
||||
case func: Function9[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8))
|
||||
case func: Function10[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9))
|
||||
case func: Function11[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10))
|
||||
case func: Function12[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11))
|
||||
case func: Function13[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12))
|
||||
case func: Function14[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13))
|
||||
case func: Function15[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14))
|
||||
case func: Function16[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15))
|
||||
case func: Function17[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16))
|
||||
case func: Function18[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17))
|
||||
case func: Function19[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17), args.apply(18))
|
||||
case func: Function20[AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef,AnyRef] => func(args.head, args.apply(1), args.apply(2), args.apply(3), args.apply(4), args.apply(5), args.apply(6), args.apply(7), args.apply(8), args.apply(9), args.apply(10), args.apply(11), args.apply(12), args.apply(13), args.apply(14), args.apply(15), args.apply(16), args.apply(17), args.apply(18), args.apply(19))
|
||||
case null => throw new IllegalStateException(s"InternalConnector have no method $methodName, it should not be called on InternalConnector")
|
||||
case _ => throw new IllegalStateException(s"InternalConnector have not correct method: $methodName")
|
||||
}
|
||||
result.asInstanceOf[AnyRef]
|
||||
val function = getFunction(methodName)
|
||||
DynamicUtil.executeFunction(methodName, function, args)
|
||||
}
|
||||
}
|
||||
|
||||
private def getFunction(methodName: String) = {
|
||||
ConnectorMethodProvider.provider.vend.getByMethodNameWithCache(methodName) map {
|
||||
case v @ JsonConnectorMethod(_, _, methodBody) =>
|
||||
dynamicMethods.computeIfAbsent(
|
||||
methodName -> methodBody,
|
||||
_ => createFunction(methodName, v.decodedMethodBody).openOrThrowException(s"InternalConnector method compile fail, method name $methodName")
|
||||
)
|
||||
case v :JsonConnectorMethod =>
|
||||
createFunction(methodName, v.decodedMethodBody).openOrThrowException(s"InternalConnector method compile fail, method name $methodName")
|
||||
}
|
||||
}
|
||||
|
||||
private val toolBox = scala.reflect.runtime.currentMirror.mkToolBox()
|
||||
// private val toolBox = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
||||
|
||||
/**
|
||||
* dynamic create function
|
||||
* @param methodName method name of connector
|
||||
@ -90,7 +55,7 @@ object InternalConnector {
|
||||
case Some(signature) =>
|
||||
val method = s"""
|
||||
|def $methodName $signature = {
|
||||
| $importStatements
|
||||
| ${DynamicUtil.importStatements}
|
||||
|
|
||||
| $methodBody
|
||||
|}
|
||||
@ -98,45 +63,16 @@ object InternalConnector {
|
||||
|$methodName _
|
||||
|""".stripMargin
|
||||
|
||||
compile(method)
|
||||
case None => Failure(s"method name $methodName not exists in Connector")
|
||||
compileScalaCode(method)
|
||||
case None => Failure(s"method name $methodName does not exist in the Connector")
|
||||
}
|
||||
|
||||
/**
|
||||
* toolBox have bug that first compile fail, second or later compile success.
|
||||
* @param code
|
||||
* @return compiled function or Failure
|
||||
*/
|
||||
private def compile(code: String): Box[Any] = {
|
||||
|
||||
val tree = try {
|
||||
toolBox.parse(code)
|
||||
} catch {
|
||||
case e: ToolBoxError =>
|
||||
return Failure(e.message)
|
||||
}
|
||||
|
||||
try {
|
||||
val func: () => Any = toolBox.compile(tree)
|
||||
Box.tryo(func())
|
||||
} catch {
|
||||
case _: ToolBoxError =>
|
||||
// try compile again
|
||||
try {
|
||||
val func: () => Any = toolBox.compile(tree)
|
||||
Box.tryo(func())
|
||||
} catch {
|
||||
case e: ToolBoxError =>
|
||||
Failure(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def callableMethods: Map[String, MethodSymbol] = {
|
||||
val dynamicMethods: Map[String, MethodSymbol] = ConnectorMethodProvider.provider.vend.getAll().map {
|
||||
case JsonConnectorMethod(_, methodName, _) =>
|
||||
methodName -> Box(methodNameToSymbols.get(methodName)).openOrThrowException(s"method name $methodName not exists in Connector")
|
||||
methodName -> Box(methodNameToSymbols.get(methodName)).openOrThrowException(s"method name $methodName does not exist in the Connector")
|
||||
} toMap
|
||||
|
||||
dynamicMethods
|
||||
@ -156,56 +92,5 @@ object InternalConnector {
|
||||
val methodSignature = StringUtils.substringBeforeLast(signature, returnType) + ":" + returnType
|
||||
methodName -> methodSignature
|
||||
}
|
||||
|
||||
/**
|
||||
* common import statements those are used by connector method body
|
||||
*/
|
||||
private val importStatements =
|
||||
"""
|
||||
|import java.net.{ConnectException, URLEncoder, UnknownHostException}
|
||||
|import java.util.Date
|
||||
|import java.util.UUID.randomUUID
|
||||
|
|
||||
|import _root_.akka.stream.StreamTcpException
|
||||
|import akka.http.scaladsl.model.headers.RawHeader
|
||||
|import akka.http.scaladsl.model.{HttpProtocol, _}
|
||||
|import akka.util.ByteString
|
||||
|import code.api.APIFailureNewStyle
|
||||
|import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions
|
||||
|import code.api.cache.Caching
|
||||
|import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType, saveConnectorMetric, _}
|
||||
|import code.api.util.ErrorMessages._
|
||||
|import code.api.util.ExampleValue._
|
||||
|import code.api.util.{APIUtil, CallContext, OBPQueryParam}
|
||||
|import code.api.v4_0_0.MockResponseHolder
|
||||
|import code.bankconnectors._
|
||||
|import code.bankconnectors.vJune2017.AuthInfo
|
||||
|import code.customer.internalMapping.MappedCustomerIdMappingProvider
|
||||
|import code.kafka.KafkaHelper
|
||||
|import code.model.dataAccess.internalMapping.MappedAccountIdMappingProvider
|
||||
|import code.util.AkkaHttpClient._
|
||||
|import code.util.Helper.MdcLoggable
|
||||
|import com.openbankproject.commons.dto.{InBoundTrait, _}
|
||||
|import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
|
||||
|import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
|
||||
|import com.openbankproject.commons.model.{ErrorMessage, TopicTrait, _}
|
||||
|import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
|// import com.tesobe.{CacheKeyFromArguments, CacheKeyOmit}
|
||||
|import net.liftweb.common.{Box, Empty, _}
|
||||
|import net.liftweb.json
|
||||
|import net.liftweb.json.Extraction.decompose
|
||||
|import net.liftweb.json.JsonDSL._
|
||||
|import net.liftweb.json.JsonParser.ParseException
|
||||
|import net.liftweb.json.{JValue, _}
|
||||
|import net.liftweb.util.Helpers.tryo
|
||||
|import org.apache.commons.lang3.StringUtils
|
||||
|
|
||||
|import scala.collection.immutable.List
|
||||
|import scala.collection.mutable.ArrayBuffer
|
||||
|import scala.concurrent.duration._
|
||||
|import scala.concurrent.{Await, Future}
|
||||
|import com.openbankproject.commons.dto._
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -26,7 +26,6 @@ Berlin 13359, Germany
|
||||
import java.net.{ConnectException, URLEncoder, UnknownHostException}
|
||||
import java.util.Date
|
||||
import java.util.UUID.randomUUID
|
||||
|
||||
import _root_.akka.stream.StreamTcpException
|
||||
import akka.http.scaladsl.model.headers.RawHeader
|
||||
import akka.http.scaladsl.model.{HttpProtocol, _}
|
||||
@ -38,7 +37,7 @@ import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType,
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.ExampleValue._
|
||||
import code.api.util.{APIUtil, CallContext, OBPQueryParam}
|
||||
import code.api.v4_0_0.MockResponseHolder
|
||||
import code.api.v4_0_0.dynamic.MockResponseHolder
|
||||
import code.bankconnectors._
|
||||
import code.bankconnectors.vJune2017.AuthInfo
|
||||
import code.customer.internalMapping.MappedCustomerIdMappingProvider
|
||||
|
||||
@ -13,7 +13,7 @@ object ConnectorMethodProvider extends SimpleInjector {
|
||||
def buildOne: MappedConnectorMethodProvider.type = MappedConnectorMethodProvider
|
||||
}
|
||||
|
||||
case class JsonConnectorMethod(internalConnectorId: Option[String], methodName: String, methodBody: String) extends JsonFieldReName{
|
||||
case class JsonConnectorMethod(connectorMethodId: Option[String], methodName: String, methodBody: String) extends JsonFieldReName{
|
||||
def decodedMethodBody: String = URLDecoder.decode(methodBody, "UTF-8")
|
||||
}
|
||||
|
||||
|
||||
@ -501,7 +501,7 @@ object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommo
|
||||
checkFormat(fieldExample.isInstanceOf[JString], s"$DynamicEntityInstanceValidateFail The property of $fieldName's 'example' field should be type ${DynamicEntityFieldType.string}")
|
||||
checkFormat(ReferenceType.isLegalReferenceValue(fieldTypeName, fieldExample.asInstanceOf[JString].s), s"$DynamicEntityInstanceValidateFail The property of $fieldName's 'example' is illegal format.")
|
||||
} else {
|
||||
// if go to here, means add new field type, but not supply corresponding value validation, that means a bug nead fix to avoid throw the follow Exception
|
||||
// if go to here, means add new field type, but not supply corresponding value validation, that means a bug need fix to avoid throw the follow Exception
|
||||
throw new RuntimeException(s"DynamicEntity $entityName's field $fieldName, type is $fieldTypeName, this type is not do validation.")
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package code.dynamicMessageDoc
|
||||
|
||||
import code.util.UUIDString
|
||||
import net.liftweb.json
|
||||
import net.liftweb.mapper._
|
||||
import scala.collection.immutable.List
|
||||
|
||||
class DynamicMessageDoc extends LongKeyedMapper[DynamicMessageDoc] with IdPK {
|
||||
|
||||
override def getSingleton = DynamicMessageDoc
|
||||
|
||||
object DynamicMessageDocId extends UUIDString(this)
|
||||
object Process extends MappedString(this, 255)
|
||||
object MessageFormat extends MappedString(this, 255)
|
||||
object Description extends MappedString(this, 255)
|
||||
object OutboundTopic extends MappedString(this, 255)
|
||||
object InboundTopic extends MappedString(this, 255)
|
||||
object ExampleOutboundMessage extends MappedText(this)
|
||||
object ExampleInboundMessage extends MappedText(this)
|
||||
object OutboundAvroSchema extends MappedText(this)
|
||||
object InboundAvroSchema extends MappedText(this)
|
||||
object AdapterImplementation extends MappedString(this, 255)
|
||||
object MethodBody extends MappedText(this)
|
||||
}
|
||||
|
||||
|
||||
object DynamicMessageDoc extends DynamicMessageDoc with LongKeyedMetaMapper[DynamicMessageDoc] {
|
||||
override def dbIndexes: List[BaseIndex[DynamicMessageDoc]] = UniqueIndex(DynamicMessageDocId) :: UniqueIndex(Process) :: super.dbIndexes
|
||||
def getJsonDynamicMessageDoc(dynamicMessageDoc: DynamicMessageDoc) = JsonDynamicMessageDoc(
|
||||
dynamicMessageDocId = Some(dynamicMessageDoc.DynamicMessageDocId.get),
|
||||
process = dynamicMessageDoc.Process.get,
|
||||
messageFormat = dynamicMessageDoc.MessageFormat.get,
|
||||
description = dynamicMessageDoc.Description.get,
|
||||
outboundTopic = dynamicMessageDoc.OutboundTopic.get,
|
||||
inboundTopic = dynamicMessageDoc.InboundTopic.get,
|
||||
exampleOutboundMessage = json.parse(dynamicMessageDoc.ExampleOutboundMessage.get),
|
||||
exampleInboundMessage = json.parse(dynamicMessageDoc.ExampleInboundMessage.get),
|
||||
outboundAvroSchema = dynamicMessageDoc.OutboundAvroSchema.get,
|
||||
inboundAvroSchema = dynamicMessageDoc.InboundAvroSchema.get,
|
||||
adapterImplementation = dynamicMessageDoc.AdapterImplementation.get,
|
||||
methodBody = dynamicMessageDoc.MethodBody.get,
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package code.dynamicMessageDoc
|
||||
|
||||
import java.net.URLDecoder
|
||||
import com.openbankproject.commons.model.JsonFieldReName
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object DynamicMessageDocProvider extends SimpleInjector {
|
||||
|
||||
val provider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: MappedDynamicMessageDocProvider.type = MappedDynamicMessageDocProvider
|
||||
}
|
||||
|
||||
case class JsonDynamicMessageDoc(
|
||||
dynamicMessageDocId: Option[String],
|
||||
process: String,
|
||||
messageFormat: String,
|
||||
description: String,
|
||||
outboundTopic: String,
|
||||
inboundTopic: String,
|
||||
exampleOutboundMessage: JValue,
|
||||
exampleInboundMessage: JValue,
|
||||
outboundAvroSchema: String,
|
||||
inboundAvroSchema: String,
|
||||
adapterImplementation: String,
|
||||
methodBody: String
|
||||
) extends JsonFieldReName{
|
||||
def decodedMethodBody: String = URLDecoder.decode(methodBody, "UTF-8")
|
||||
}
|
||||
|
||||
trait DynamicMessageDocProvider {
|
||||
|
||||
def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc]
|
||||
def getByProcess(process: String): Box[JsonDynamicMessageDoc]
|
||||
def getAll(): List[JsonDynamicMessageDoc]
|
||||
|
||||
def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def deleteById(dynamicMessageDocId: String): Box[Boolean]
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
package code.dynamicMessageDoc
|
||||
|
||||
import code.api.cache.Caching
|
||||
import code.api.util.APIUtil
|
||||
import com.tesobe.CacheKeyFromArguments
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers.tryo
|
||||
import net.liftweb.util.Props
|
||||
import java.util.UUID.randomUUID
|
||||
import code.util.Helper
|
||||
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
|
||||
|
||||
private val getDynamicMessageDocTTL : Int = {
|
||||
if(Props.testMode) 0
|
||||
else APIUtil.getPropsValue(s"dynamicMessageDoc.cache.ttl.seconds", "40").toInt
|
||||
}
|
||||
|
||||
override def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc] =
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId))
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
|
||||
override def getByProcess(process: String): Box[JsonDynamicMessageDoc] =
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.Process, process))
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
|
||||
|
||||
override def getAll(): List[JsonDynamicMessageDoc] = {
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicMessageDocTTL second) {
|
||||
DynamicMessageDoc.findAll()
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}}
|
||||
}
|
||||
|
||||
override def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]=
|
||||
tryo {
|
||||
DynamicMessageDoc.create
|
||||
.DynamicMessageDocId(APIUtil.generateUUID())
|
||||
.Process(entity.process)
|
||||
.MessageFormat(entity.messageFormat)
|
||||
.Description(entity.description)
|
||||
.OutboundTopic(entity.outboundTopic)
|
||||
.InboundTopic(entity.inboundTopic)
|
||||
.ExampleOutboundMessage(Helper.prettyJson(entity.exampleOutboundMessage))
|
||||
.ExampleInboundMessage(Helper.prettyJson(entity.exampleInboundMessage))
|
||||
.OutboundAvroSchema(entity.outboundAvroSchema)
|
||||
.InboundAvroSchema(entity.inboundAvroSchema)
|
||||
.AdapterImplementation(entity.adapterImplementation)
|
||||
.MethodBody(entity.methodBody)
|
||||
.saveMe()
|
||||
}.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
|
||||
|
||||
override def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc] = {
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))) match {
|
||||
case Full(v) =>
|
||||
tryo {
|
||||
v.DynamicMessageDocId(entity.dynamicMessageDocId.getOrElse(""))
|
||||
.Process(entity.process)
|
||||
.MessageFormat(entity.messageFormat)
|
||||
.Description(entity.description)
|
||||
.OutboundTopic(entity.outboundTopic)
|
||||
.InboundTopic(entity.inboundTopic)
|
||||
.ExampleOutboundMessage(Helper.prettyJson(entity.exampleOutboundMessage))
|
||||
.ExampleInboundMessage(Helper.prettyJson(entity.exampleInboundMessage))
|
||||
.OutboundAvroSchema(entity.outboundAvroSchema)
|
||||
.InboundAvroSchema(entity.inboundAvroSchema)
|
||||
.AdapterImplementation(entity.adapterImplementation)
|
||||
.MethodBody(entity.methodBody)
|
||||
.saveMe()
|
||||
}.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
case _ => Empty
|
||||
}
|
||||
}
|
||||
|
||||
override def deleteById(id: String): Box[Boolean] = tryo {
|
||||
DynamicMessageDoc.bulkDelete_!!(By(DynamicMessageDoc.DynamicMessageDocId, id))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package code.dynamicResourceDoc
|
||||
|
||||
import code.util.UUIDString
|
||||
import net.liftweb.json
|
||||
import net.liftweb.mapper._
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK {
|
||||
|
||||
override def getSingleton = DynamicResourceDoc
|
||||
|
||||
object DynamicResourceDocId extends UUIDString(this)
|
||||
object PartialFunctionName extends MappedString(this, 255)
|
||||
object RequestVerb extends MappedString(this, 255)
|
||||
object RequestUrl extends MappedString(this, 255)
|
||||
object Summary extends MappedString(this, 255)
|
||||
object Description extends MappedString(this, 255)
|
||||
object ExampleRequestBody extends MappedString(this, 255)
|
||||
object SuccessResponseBody extends MappedString(this, 255)
|
||||
object ErrorResponseBodies extends MappedString(this, 255)
|
||||
object Tags extends MappedString(this, 255)
|
||||
object Roles extends MappedString(this, 255)
|
||||
object MethodBody extends MappedText(this)
|
||||
|
||||
}
|
||||
|
||||
|
||||
object DynamicResourceDoc extends DynamicResourceDoc with LongKeyedMetaMapper[DynamicResourceDoc] {
|
||||
override def dbIndexes: List[BaseIndex[DynamicResourceDoc]] = UniqueIndex(DynamicResourceDocId) :: UniqueIndex(RequestUrl,RequestVerb) :: super.dbIndexes
|
||||
def getJsonDynamicResourceDoc(dynamicResourceDoc: DynamicResourceDoc) = JsonDynamicResourceDoc(
|
||||
dynamicResourceDocId = Some(dynamicResourceDoc.DynamicResourceDocId.get),
|
||||
methodBody = dynamicResourceDoc.MethodBody.get,
|
||||
partialFunctionName = dynamicResourceDoc.PartialFunctionName.get,
|
||||
requestVerb = dynamicResourceDoc.RequestVerb.get,
|
||||
requestUrl = dynamicResourceDoc.RequestUrl.get,
|
||||
summary = dynamicResourceDoc.Summary.get,
|
||||
description = dynamicResourceDoc.Description.get,
|
||||
exampleRequestBody = Option(dynamicResourceDoc.ExampleRequestBody.get).filter(StringUtils.isNotBlank).map(json.parse),
|
||||
successResponseBody = Option(dynamicResourceDoc.SuccessResponseBody.get).filter(StringUtils.isNotBlank).map(json.parse),
|
||||
errorResponseBodies = dynamicResourceDoc.ErrorResponseBodies.get,
|
||||
tags = dynamicResourceDoc.Tags.get,
|
||||
roles = dynamicResourceDoc.Roles.get
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package code.dynamicResourceDoc
|
||||
|
||||
import com.openbankproject.commons.model.JsonFieldReName
|
||||
import com.openbankproject.commons.util.JsonAble
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.JsonAST.JNothing
|
||||
import net.liftweb.json.{Formats, JValue, JsonAST}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.net.URLDecoder
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object DynamicResourceDocProvider extends SimpleInjector {
|
||||
|
||||
val provider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: MappedDynamicResourceDocProvider.type = MappedDynamicResourceDocProvider
|
||||
}
|
||||
|
||||
case class JsonDynamicResourceDoc(
|
||||
dynamicResourceDocId: Option[String],
|
||||
methodBody: String,
|
||||
partialFunctionName: String,
|
||||
requestVerb: String,
|
||||
requestUrl: String,
|
||||
summary: String,
|
||||
description: String,
|
||||
exampleRequestBody: Option[JValue],
|
||||
successResponseBody: Option[JValue],
|
||||
errorResponseBodies: String,
|
||||
tags: String,
|
||||
roles: String
|
||||
) extends JsonFieldReName {
|
||||
def decodedMethodBody: String = URLDecoder.decode(methodBody, "UTF-8")
|
||||
}
|
||||
|
||||
trait DynamicResourceDocProvider {
|
||||
|
||||
def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc]
|
||||
def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc]
|
||||
|
||||
def getAll(): List[JsonDynamicResourceDoc] = getAllAndConvert(identity)
|
||||
|
||||
def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T]
|
||||
|
||||
def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def deleteById(dynamicResourceDocId: String): Box[Boolean]
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
package code.dynamicResourceDoc
|
||||
|
||||
import code.api.cache.Caching
|
||||
import code.api.util.APIUtil
|
||||
import com.tesobe.CacheKeyFromArguments
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.json
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers.tryo
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import java.util.UUID.randomUUID
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
|
||||
|
||||
private val getDynamicResourceDocTTL : Int = {
|
||||
if(Props.testMode) 0
|
||||
else if(Props.devMode) 10
|
||||
else APIUtil.getPropsValue(s"dynamicResourceDoc.cache.ttl.seconds", "40").toInt
|
||||
}
|
||||
|
||||
override def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
|
||||
override def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.RequestVerb, requestVerb), By(DynamicResourceDoc.RequestUrl, requestUrl))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
|
||||
override def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T] = {
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocTTL second) {
|
||||
DynamicResourceDoc.findAll()
|
||||
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
|
||||
}}
|
||||
}
|
||||
|
||||
override def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]=
|
||||
tryo {
|
||||
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
|
||||
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
|
||||
|
||||
DynamicResourceDoc.create
|
||||
.DynamicResourceDocId(APIUtil.generateUUID())
|
||||
.PartialFunctionName(entity.partialFunctionName)
|
||||
.RequestVerb(entity.requestVerb)
|
||||
.RequestUrl(entity.requestUrl)
|
||||
.Summary(entity.summary)
|
||||
.Description(entity.description)
|
||||
.ExampleRequestBody(requestBody)
|
||||
.SuccessResponseBody(responseBody)
|
||||
.ErrorResponseBodies(entity.errorResponseBodies)
|
||||
.Tags(entity.tags)
|
||||
.Roles(entity.roles)
|
||||
.MethodBody(entity.methodBody)
|
||||
.saveMe()
|
||||
}.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
|
||||
|
||||
override def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc] = {
|
||||
DynamicResourceDoc.find(By(DynamicResourceDoc.DynamicResourceDocId, entity.dynamicResourceDocId.getOrElse(""))) match {
|
||||
case Full(v) =>
|
||||
tryo {
|
||||
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
|
||||
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
|
||||
v.PartialFunctionName(entity.partialFunctionName)
|
||||
.RequestVerb(entity.requestVerb)
|
||||
.RequestUrl(entity.requestUrl)
|
||||
.Summary(entity.summary)
|
||||
.Description(entity.description)
|
||||
.ExampleRequestBody(requestBody)
|
||||
.SuccessResponseBody(responseBody)
|
||||
.ErrorResponseBodies(entity.errorResponseBodies)
|
||||
.Tags(entity.tags)
|
||||
.Roles(entity.roles)
|
||||
.MethodBody(entity.methodBody)
|
||||
.saveMe()
|
||||
}.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
case _ => Empty
|
||||
}
|
||||
}
|
||||
|
||||
override def deleteById(id: String): Box[Boolean] = tryo {
|
||||
DynamicResourceDoc.bulkDelete_!!(By(DynamicResourceDoc.DynamicResourceDocId, id))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package code.entitlement
|
||||
|
||||
|
||||
import code.api.v4_0_0.DynamicEntityInfo
|
||||
import code.api.v4_0_0.dynamic.DynamicEntityInfo
|
||||
import code.util.{MappedUUID, UUIDString}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.mapper._
|
||||
|
||||
@ -40,6 +40,8 @@ import sun.security.provider.X509Factory
|
||||
|
||||
object RunMTLSWebApp extends App {
|
||||
val servletContextPath = "/"
|
||||
//set run mode value to "development", So the value is true of Props.devMode
|
||||
System.setProperty("run.mode", "development")
|
||||
|
||||
{
|
||||
val tempHTTPContext = JProxy.newProxyInstance(this.getClass.getClassLoader, Array(classOf[HTTPContext]),
|
||||
|
||||
@ -32,11 +32,12 @@ import java.lang.reflect.{Proxy => JProxy}
|
||||
import net.liftweb.http.LiftRules
|
||||
import net.liftweb.http.provider.HTTPContext
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.session.SessionHandler
|
||||
import org.eclipse.jetty.webapp.WebAppContext
|
||||
|
||||
object RunWebApp extends App {
|
||||
val servletContextPath = "/"
|
||||
//set run mode value to "development", So the value is true of Props.devMode
|
||||
System.setProperty("run.mode", "development")
|
||||
|
||||
/**
|
||||
* The above code is related to Chicken or the egg dilemma.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package code.api.v3_1_0
|
||||
package code.api.ResourceDocs1_4_0
|
||||
|
||||
import java.util
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH
|
||||
Copyright (C) 2011-2021, TESOBE GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
@ -83,11 +83,11 @@ class ConnectorMethodTest extends V400ServerSetup {
|
||||
|
||||
connectorMethod.methodName should be (postConnectorMethod.methodName)
|
||||
connectorMethod.methodBody should be (postConnectorMethod.methodBody)
|
||||
connectorMethod.internalConnectorId shouldNot be (null)
|
||||
connectorMethod.connectorMethodId shouldNot be (null)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint2")
|
||||
val requestGet = (v4_0_0_Request / "management" / "connector-methods" / {connectorMethod.internalConnectorId.getOrElse("")}).GET <@ (user1)
|
||||
val requestGet = (v4_0_0_Request / "management" / "connector-methods" / {connectorMethod.connectorMethodId.getOrElse("")}).GET <@ (user1)
|
||||
|
||||
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
@ -98,7 +98,7 @@ class ConnectorMethodTest extends V400ServerSetup {
|
||||
|
||||
connectorMethodJsonGet400.methodName should be (postConnectorMethod.methodName)
|
||||
connectorMethodJsonGet400.methodBody should be (postConnectorMethod.methodBody)
|
||||
connectorMethod.internalConnectorId should be (connectorMethodJsonGet400.internalConnectorId)
|
||||
connectorMethod.connectorMethodId should be (connectorMethodJsonGet400.connectorMethodId)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint3")
|
||||
@ -116,11 +116,11 @@ class ConnectorMethodTest extends V400ServerSetup {
|
||||
val connectorMethods = connectorMethodsJsonGetAll(0)
|
||||
(connectorMethods \ "method_name").values.toString should equal (postConnectorMethod.methodName)
|
||||
(connectorMethods \ "method_body").values.toString should equal (postConnectorMethod.methodBody)
|
||||
(connectorMethods \ "internal_connector_id").values.toString should be (connectorMethodJsonGet400.internalConnectorId.get)
|
||||
(connectorMethods \ "connector_method_id").values.toString should be (connectorMethodJsonGet400.connectorMethodId.get)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint4")
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "connector-methods" / {connectorMethod.internalConnectorId.getOrElse("")}).PUT <@ (user1)
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "connector-methods" / {connectorMethod.connectorMethodId.getOrElse("")}).PUT <@ (user1)
|
||||
|
||||
lazy val postConnectorMethodMethodBody = SwaggerDefinitionsJSON.jsonConnectorMethodMethodBody
|
||||
|
||||
@ -136,7 +136,7 @@ class ConnectorMethodTest extends V400ServerSetup {
|
||||
|
||||
connectorMethodJsonGetAfterUpdated.methodBody should be (postConnectorMethodMethodBody.methodBody)
|
||||
connectorMethodJsonGetAfterUpdated.methodName should be (connectorMethodJsonGet400.methodName)
|
||||
connectorMethodJsonGetAfterUpdated.internalConnectorId should be (connectorMethodJsonGet400.internalConnectorId)
|
||||
connectorMethodJsonGetAfterUpdated.connectorMethodId should be (connectorMethodJsonGet400.connectorMethodId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ class ConnectorMethodTest extends V400ServerSetup {
|
||||
|
||||
connectorMethod.methodName should be (postConnectorMethod.methodName)
|
||||
connectorMethod.methodBody should be (postConnectorMethod.methodBody)
|
||||
connectorMethod.internalConnectorId shouldNot be (null)
|
||||
connectorMethod.connectorMethodId shouldNot be (null)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint1 with the same methodName")
|
||||
|
||||
@ -0,0 +1,250 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
*/
|
||||
package code.api.v4_0_0
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ErrorMessages.{UserHasMissingRoles, DynamicMessageDocNotFound}
|
||||
import code.api.util.{ApiRole}
|
||||
import code.api.v4_0_0.APIMethods400.Implementations4_0_0
|
||||
import code.dynamicMessageDoc.{JsonDynamicMessageDoc}
|
||||
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.JArray
|
||||
import net.liftweb.json.Serialization.write
|
||||
import org.scalatest.Tag
|
||||
|
||||
|
||||
class DynamicMessageDocTest 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.createDynamicMessageDoc))
|
||||
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.updateDynamicMessageDoc))
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicMessageDoc))
|
||||
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getAllDynamicMessageDocs))
|
||||
object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteDynamicMessageDoc))
|
||||
|
||||
feature("Test the DynamicMessageDoc endpoints") {
|
||||
scenario("We create my DynamicMessageDoc and get,update", ApiEndpoint1,ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canCreateDynamicMessageDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canGetDynamicMessageDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canGetAllDynamicMessageDocs.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canUpdateDynamicMessageDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canDeleteDynamicMessageDoc.toString)
|
||||
|
||||
val request = (v4_0_0_Request / "management" / "dynamic-message-docs").POST <@ (user1)
|
||||
|
||||
lazy val postDynamicMessageDoc = SwaggerDefinitionsJSON.jsonDynamicMessageDoc.copy(dynamicMessageDocId = None)
|
||||
|
||||
val postBody = write(postDynamicMessageDoc)
|
||||
val response = makePostRequest(request, postBody)
|
||||
Then("We should get a 201")
|
||||
response.code should equal(201)
|
||||
|
||||
val dynamicMessageDoc = response.body.extract[JsonDynamicMessageDoc]
|
||||
|
||||
dynamicMessageDoc.dynamicMessageDocId shouldNot be (null)
|
||||
dynamicMessageDoc.process should be (postDynamicMessageDoc.process)
|
||||
dynamicMessageDoc.messageFormat should be (postDynamicMessageDoc.messageFormat)
|
||||
dynamicMessageDoc.description should be (postDynamicMessageDoc.description)
|
||||
dynamicMessageDoc.outboundTopic should be (postDynamicMessageDoc.outboundTopic)
|
||||
dynamicMessageDoc.inboundTopic should be (postDynamicMessageDoc.inboundTopic)
|
||||
dynamicMessageDoc.exampleOutboundMessage should be (postDynamicMessageDoc.exampleOutboundMessage)
|
||||
dynamicMessageDoc.exampleInboundMessage should be (postDynamicMessageDoc.exampleInboundMessage)
|
||||
dynamicMessageDoc.outboundAvroSchema should be (postDynamicMessageDoc.outboundAvroSchema)
|
||||
dynamicMessageDoc.inboundAvroSchema should be (postDynamicMessageDoc.inboundAvroSchema)
|
||||
dynamicMessageDoc.adapterImplementation should be (postDynamicMessageDoc.adapterImplementation)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint2")
|
||||
val requestGet = (v4_0_0_Request / "management" / "dynamic-message-docs" / {dynamicMessageDoc.dynamicMessageDocId.getOrElse("")}).GET <@ (user1)
|
||||
|
||||
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
Then("We should get a 200")
|
||||
responseGet.code should equal(200)
|
||||
|
||||
val dynamicMessageDocJsonGet400 = responseGet.body.extract[JsonDynamicMessageDoc]
|
||||
|
||||
dynamicMessageDoc.dynamicMessageDocId shouldNot be (postDynamicMessageDoc.dynamicMessageDocId)
|
||||
dynamicMessageDoc.process should be (postDynamicMessageDoc.process)
|
||||
dynamicMessageDoc.messageFormat should be (postDynamicMessageDoc.messageFormat)
|
||||
dynamicMessageDoc.description should be (postDynamicMessageDoc.description)
|
||||
dynamicMessageDoc.outboundTopic should be (postDynamicMessageDoc.outboundTopic)
|
||||
dynamicMessageDoc.inboundTopic should be (postDynamicMessageDoc.inboundTopic)
|
||||
dynamicMessageDoc.exampleOutboundMessage should be (postDynamicMessageDoc.exampleOutboundMessage)
|
||||
dynamicMessageDoc.exampleInboundMessage should be (postDynamicMessageDoc.exampleInboundMessage)
|
||||
dynamicMessageDoc.outboundAvroSchema should be (postDynamicMessageDoc.outboundAvroSchema)
|
||||
dynamicMessageDoc.inboundAvroSchema should be (postDynamicMessageDoc.inboundAvroSchema)
|
||||
dynamicMessageDoc.adapterImplementation should be (postDynamicMessageDoc.adapterImplementation)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint3")
|
||||
val requestGetAll = (v4_0_0_Request / "management" / "dynamic-message-docs").GET <@ (user1)
|
||||
|
||||
|
||||
val responseGetAll = makeGetRequest(requestGetAll)
|
||||
Then("We should get a 200")
|
||||
responseGetAll.code should equal(200)
|
||||
|
||||
val dynamicMessageDocsJsonGetAll = responseGetAll.body \ "dynamic-message-docs"
|
||||
|
||||
dynamicMessageDocsJsonGetAll shouldBe a [JArray]
|
||||
|
||||
val dynamicMessageDocs = dynamicMessageDocsJsonGetAll(0)
|
||||
|
||||
(dynamicMessageDocs \ "dynamic_message_doc_id").values.toString should equal (dynamicMessageDoc.dynamicMessageDocId.get)
|
||||
(dynamicMessageDocs \ "process").values.toString should equal (postDynamicMessageDoc.process)
|
||||
(dynamicMessageDocs \ "message_format").values.toString should equal (postDynamicMessageDoc.messageFormat)
|
||||
(dynamicMessageDocs \ "description").values.toString should equal (postDynamicMessageDoc.description)
|
||||
(dynamicMessageDocs \ "outbound_topic").values.toString should equal (postDynamicMessageDoc.outboundTopic)
|
||||
(dynamicMessageDocs \ "inbound_topic").values.toString should equal (postDynamicMessageDoc.inboundTopic)
|
||||
(dynamicMessageDocs \ "example_outbound_message") should equal (postDynamicMessageDoc.exampleOutboundMessage)
|
||||
(dynamicMessageDocs \ "example_inbound_message") should equal (postDynamicMessageDoc.exampleInboundMessage)
|
||||
(dynamicMessageDocs \ "outbound_avro_schema").values.toString should equal (postDynamicMessageDoc.outboundAvroSchema)
|
||||
(dynamicMessageDocs \ "inbound_avro_schema").values.toString should equal (postDynamicMessageDoc.inboundAvroSchema)
|
||||
(dynamicMessageDocs \ "adapter_implementation").values.toString should equal (postDynamicMessageDoc.adapterImplementation)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint4")
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "dynamic-message-docs" / {dynamicMessageDoc.dynamicMessageDocId.getOrElse("")}).PUT <@ (user1)
|
||||
|
||||
val postDynamicMessageDocBody = SwaggerDefinitionsJSON.jsonDynamicMessageDoc.copy(process="getAccount")
|
||||
|
||||
val responseUpdate = makePutRequest(requestUpdate,write(postDynamicMessageDocBody))
|
||||
Then("We should get a 200")
|
||||
responseUpdate.code should equal(200)
|
||||
|
||||
val responseGetAfterUpdated = makeGetRequest(requestGet)
|
||||
Then("We should get a 200")
|
||||
responseGetAfterUpdated.code should equal(200)
|
||||
|
||||
val dynamicMessageDocJsonGetAfterUpdated = responseGetAfterUpdated.body.extract[JsonDynamicMessageDoc]
|
||||
|
||||
dynamicMessageDocJsonGetAfterUpdated.process should be (postDynamicMessageDocBody.process)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint5")
|
||||
val requestDelete = (v4_0_0_Request / "management" / "dynamic-message-docs" / {dynamicMessageDoc.dynamicMessageDocId.getOrElse("")}).DELETE <@ (user1)
|
||||
|
||||
val responseDelete = makeDeleteRequest(requestDelete)
|
||||
Then("We should get a 204")
|
||||
responseDelete.code should equal(204)
|
||||
|
||||
val responseGetAfterDeleted = makeGetRequest(requestGet)
|
||||
Then("We should get a 400")
|
||||
Then("We should get a 400")
|
||||
responseGetAfterDeleted.code should equal(400)
|
||||
responseGetAfterDeleted.body.extract[ErrorMessage].message contains(DynamicMessageDocNotFound) should be (true)
|
||||
}
|
||||
}
|
||||
|
||||
feature("Test the DynamicMessageDoc endpoints error cases") {
|
||||
// may need it later
|
||||
// scenario("We create my DynamicMessageDoc -- duplicated DynamicMessageDoc Name", ApiEndpoint1, VersionOfApi) {
|
||||
// When("We make a request v4.0.0")
|
||||
//
|
||||
// Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canCreateDynamicMessageDoc.toString)
|
||||
//
|
||||
//
|
||||
// val request = (v4_0_0_Request / "management" / "dynamic-message-docs").POST <@ (user1)
|
||||
//
|
||||
// lazy val postDynamicMessageDoc = SwaggerDefinitionsJSON.jsonDynamicMessageDoc
|
||||
//
|
||||
// val response = makePostRequest(request, write(postDynamicMessageDoc))
|
||||
// Then("We should get a 201")
|
||||
// response.code should equal(201)
|
||||
//
|
||||
// val dynamicMessageDoc = response.body.extract[JsonDynamicMessageDoc]
|
||||
//
|
||||
// Then(s"we test the $ApiEndpoint1 with the same methodName")
|
||||
//
|
||||
// val response2 = makePostRequest(request, write(postDynamicMessageDoc))
|
||||
// Then("We should get a 400")
|
||||
// response2.code should equal(400)
|
||||
// response2.body.extract[ErrorMessage].message contains(DynamicMessageDocAlreadyExists) should be (true)
|
||||
// }
|
||||
|
||||
scenario("We create/get/getAll/update my DynamicMessageDoc without our proper roles", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
|
||||
val request = (v4_0_0_Request / "management" / "dynamic-message-docs").POST <@ (user1)
|
||||
lazy val postDynamicMessageDoc = SwaggerDefinitionsJSON.jsonDynamicMessageDoc
|
||||
val response = makePostRequest(request, write(postDynamicMessageDoc))
|
||||
Then("We should get a 403")
|
||||
response.code should equal(403)
|
||||
response.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanCreateDynamicMessageDoc}")
|
||||
|
||||
Then(s"we test the $ApiEndpoint2")
|
||||
val requestGet = (v4_0_0_Request / "management" / "dynamic-message-docs" / "xx").GET <@ (user1)
|
||||
|
||||
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
Then("We should get a 403")
|
||||
responseGet.code should equal(403)
|
||||
responseGet.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanGetDynamicMessageDoc}")
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint3")
|
||||
val requestGetAll = (v4_0_0_Request / "management" / "dynamic-message-docs").GET <@ (user1)
|
||||
|
||||
val responseGetAll = makeGetRequest(requestGetAll)
|
||||
responseGetAll.code should equal(403)
|
||||
responseGetAll.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanGetAllDynamicMessageDocs}")
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint4")
|
||||
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "dynamic-message-docs" / "xx").PUT <@ (user1)
|
||||
val responseUpdate = makePutRequest(requestUpdate,write(postDynamicMessageDoc))
|
||||
|
||||
responseUpdate.code should equal(403)
|
||||
responseUpdate.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanUpdateDynamicMessageDoc}")
|
||||
|
||||
Then(s"we test the $ApiEndpoint5")
|
||||
|
||||
val requestDelete = (v4_0_0_Request / "management" / "dynamic-message-docs" / "xx").DELETE <@ (user1)
|
||||
val responseDelete = makeDeleteRequest(requestDelete)
|
||||
|
||||
responseDelete.code should equal(403)
|
||||
responseDelete.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanDeleteDynamicMessageDoc}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,248 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
*/
|
||||
package code.api.v4_0_0
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ErrorMessages.{DynamicResourceDocAlreadyExists, DynamicResourceDocNotFound, UserHasMissingRoles}
|
||||
import code.api.util.ApiRole
|
||||
import code.api.v4_0_0.APIMethods400.Implementations4_0_0
|
||||
import code.dynamicResourceDoc.JsonDynamicResourceDoc
|
||||
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
|
||||
import net.liftweb.json.JArray
|
||||
import net.liftweb.json.Serialization.write
|
||||
import org.scalatest.Tag
|
||||
|
||||
|
||||
class DynamicResourceDocTest 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.createDynamicResourceDoc))
|
||||
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.updateDynamicResourceDoc))
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicResourceDoc))
|
||||
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getAllDynamicResourceDocs))
|
||||
object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteDynamicResourceDoc))
|
||||
|
||||
feature("Test the DynamicResourceDoc endpoints") {
|
||||
scenario("We create my DynamicResourceDoc and get,update", ApiEndpoint1,ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canCreateDynamicResourceDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canGetDynamicResourceDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canGetAllDynamicResourceDocs.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canUpdateDynamicResourceDoc.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canDeleteDynamicResourceDoc.toString)
|
||||
|
||||
val request = (v4_0_0_Request / "management" / "dynamic-resource-docs").POST <@ (user1)
|
||||
|
||||
lazy val postDynamicResourceDoc = SwaggerDefinitionsJSON.jsonDynamicResourceDoc.copy(dynamicResourceDocId = None)
|
||||
|
||||
val response = makePostRequest(request, write(postDynamicResourceDoc))
|
||||
Then("We should get a 201")
|
||||
response.code should equal(201)
|
||||
|
||||
val dynamicResourceDoc = response.body.extract[JsonDynamicResourceDoc]
|
||||
|
||||
dynamicResourceDoc.dynamicResourceDocId shouldNot be (null)
|
||||
dynamicResourceDoc.methodBody should be (postDynamicResourceDoc.methodBody)
|
||||
dynamicResourceDoc.partialFunctionName should be (postDynamicResourceDoc.partialFunctionName)
|
||||
dynamicResourceDoc.requestVerb should be (postDynamicResourceDoc.requestVerb)
|
||||
dynamicResourceDoc.requestUrl should be (postDynamicResourceDoc.requestUrl)
|
||||
dynamicResourceDoc.summary should be (postDynamicResourceDoc.summary)
|
||||
dynamicResourceDoc.description should be (postDynamicResourceDoc.description)
|
||||
dynamicResourceDoc.errorResponseBodies should be (postDynamicResourceDoc.errorResponseBodies)
|
||||
dynamicResourceDoc.tags should be (postDynamicResourceDoc.tags)
|
||||
|
||||
dynamicResourceDoc.exampleRequestBody should be(postDynamicResourceDoc.exampleRequestBody)
|
||||
dynamicResourceDoc.successResponseBody should be(postDynamicResourceDoc.successResponseBody)
|
||||
|
||||
Then(s"we test the $ApiEndpoint2")
|
||||
val requestGet = (v4_0_0_Request / "management" / "dynamic-resource-docs" / {dynamicResourceDoc.dynamicResourceDocId.getOrElse("")}).GET <@ (user1)
|
||||
|
||||
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
Then("We should get a 200")
|
||||
responseGet.code should equal(200)
|
||||
|
||||
val dynamicResourceDocJsonGet400 = responseGet.body.extract[JsonDynamicResourceDoc]
|
||||
|
||||
dynamicResourceDoc.dynamicResourceDocId shouldNot be (postDynamicResourceDoc.dynamicResourceDocId)
|
||||
dynamicResourceDoc.methodBody should be (postDynamicResourceDoc.methodBody)
|
||||
dynamicResourceDoc.partialFunctionName should be (postDynamicResourceDoc.partialFunctionName)
|
||||
dynamicResourceDoc.requestVerb should be (postDynamicResourceDoc.requestVerb)
|
||||
dynamicResourceDoc.requestUrl should be (postDynamicResourceDoc.requestUrl)
|
||||
dynamicResourceDoc.summary should be (postDynamicResourceDoc.summary)
|
||||
dynamicResourceDoc.description should be (postDynamicResourceDoc.description)
|
||||
dynamicResourceDoc.errorResponseBodies should be (postDynamicResourceDoc.errorResponseBodies)
|
||||
dynamicResourceDoc.tags should be (postDynamicResourceDoc.tags)
|
||||
|
||||
dynamicResourceDoc.exampleRequestBody should be(postDynamicResourceDoc.exampleRequestBody)
|
||||
dynamicResourceDoc.successResponseBody should be(postDynamicResourceDoc.successResponseBody)
|
||||
|
||||
Then(s"we test the $ApiEndpoint3")
|
||||
val requestGetAll = (v4_0_0_Request / "management" / "dynamic-resource-docs").GET <@ (user1)
|
||||
|
||||
|
||||
val responseGetAll = makeGetRequest(requestGetAll)
|
||||
Then("We should get a 200")
|
||||
responseGetAll.code should equal(200)
|
||||
|
||||
val dynamicResourceDocsJsonGetAll = responseGetAll.body \ "dynamic-resource-docs"
|
||||
|
||||
dynamicResourceDocsJsonGetAll shouldBe a [JArray]
|
||||
|
||||
val dynamicResourceDocs = dynamicResourceDocsJsonGetAll(0)
|
||||
|
||||
(dynamicResourceDocs \ "dynamic_resource_doc_id").values.toString should equal (dynamicResourceDoc.dynamicResourceDocId.get)
|
||||
(dynamicResourceDocs \ "partial_function_name").values.toString should equal (postDynamicResourceDoc.partialFunctionName)
|
||||
(dynamicResourceDocs \ "request_verb").values.toString should equal (postDynamicResourceDoc.requestVerb)
|
||||
(dynamicResourceDocs \ "request_url").values.toString should equal (postDynamicResourceDoc.requestUrl)
|
||||
(dynamicResourceDocs \ "summary").values.toString should equal (postDynamicResourceDoc.summary)
|
||||
(dynamicResourceDocs \ "description").values.toString should equal (postDynamicResourceDoc.description)
|
||||
(dynamicResourceDocs \ "example_request_body") should equal (postDynamicResourceDoc.exampleRequestBody.orNull)
|
||||
(dynamicResourceDocs \ "success_response_body") should equal (postDynamicResourceDoc.successResponseBody.orNull)
|
||||
(dynamicResourceDocs \ "error_response_bodies").values.toString should equal (postDynamicResourceDoc.errorResponseBodies)
|
||||
(dynamicResourceDocs \ "tags").values.toString should equal (postDynamicResourceDoc.tags)
|
||||
(dynamicResourceDocs \ "method_body").values.toString should equal (postDynamicResourceDoc.methodBody)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint4")
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "dynamic-resource-docs" / {dynamicResourceDoc.dynamicResourceDocId.getOrElse("")}).PUT <@ (user1)
|
||||
|
||||
val postDynamicResourceDocBody = SwaggerDefinitionsJSON.jsonDynamicResourceDoc.copy(partialFunctionName="getAccount")
|
||||
|
||||
val responseUpdate = makePutRequest(requestUpdate,write(postDynamicResourceDocBody))
|
||||
Then("We should get a 200")
|
||||
responseUpdate.code should equal(200)
|
||||
|
||||
val responseGetAfterUpdated = makeGetRequest(requestGet)
|
||||
Then("We should get a 200")
|
||||
responseGetAfterUpdated.code should equal(200)
|
||||
|
||||
val dynamicResourceDocJsonGetAfterUpdated = responseGetAfterUpdated.body.extract[JsonDynamicResourceDoc]
|
||||
|
||||
dynamicResourceDocJsonGetAfterUpdated.partialFunctionName should be (postDynamicResourceDocBody.partialFunctionName)
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint5")
|
||||
val requestDelete = (v4_0_0_Request / "management" / "dynamic-resource-docs" / {dynamicResourceDoc.dynamicResourceDocId.getOrElse("")}).DELETE <@ (user1)
|
||||
|
||||
val responseDelete = makeDeleteRequest(requestDelete)
|
||||
Then("We should get a 204")
|
||||
responseDelete.code should equal(204)
|
||||
|
||||
val responseGetAfterDeleted = makeGetRequest(requestGet)
|
||||
Then("We should get a 400")
|
||||
Then("We should get a 400")
|
||||
responseGetAfterDeleted.code should equal(400)
|
||||
responseGetAfterDeleted.body.extract[ErrorMessage].message contains(DynamicResourceDocNotFound) should be (true)
|
||||
}
|
||||
}
|
||||
|
||||
feature("Test the DynamicResourceDoc endpoints error cases") {
|
||||
scenario("We create my DynamicResourceDoc -- duplicated DynamicResourceDoc Name", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.canCreateDynamicResourceDoc.toString)
|
||||
|
||||
|
||||
val request = (v4_0_0_Request / "management" / "dynamic-resource-docs").POST <@ (user1)
|
||||
|
||||
lazy val postDynamicResourceDoc = SwaggerDefinitionsJSON.jsonDynamicResourceDoc
|
||||
|
||||
val response = makePostRequest(request, write(postDynamicResourceDoc))
|
||||
Then("We should get a 201")
|
||||
response.code should equal(201)
|
||||
|
||||
Then(s"we test the $ApiEndpoint1 with the same methodName")
|
||||
|
||||
val response2 = makePostRequest(request, write(postDynamicResourceDoc))
|
||||
Then("We should get a 400")
|
||||
response2.code should equal(400)
|
||||
response2.body.extract[ErrorMessage].message contains(DynamicResourceDocAlreadyExists) should be (true)
|
||||
|
||||
}
|
||||
|
||||
scenario("We create/get/getAll/update my DynamicResourceDoc without our proper roles", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
|
||||
val request = (v4_0_0_Request / "management" / "dynamic-resource-docs").POST <@ (user1)
|
||||
lazy val postDynamicResourceDoc = SwaggerDefinitionsJSON.jsonDynamicResourceDoc
|
||||
val response = makePostRequest(request, write(postDynamicResourceDoc))
|
||||
Then("We should get a 403")
|
||||
response.code should equal(403)
|
||||
response.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanCreateDynamicResourceDoc}")
|
||||
|
||||
Then(s"we test the $ApiEndpoint2")
|
||||
val requestGet = (v4_0_0_Request / "management" / "dynamic-resource-docs" / "xx").GET <@ (user1)
|
||||
|
||||
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
Then("We should get a 403")
|
||||
responseGet.code should equal(403)
|
||||
responseGet.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanGetDynamicResourceDoc}")
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint3")
|
||||
val requestGetAll = (v4_0_0_Request / "management" / "dynamic-resource-docs").GET <@ (user1)
|
||||
|
||||
val responseGetAll = makeGetRequest(requestGetAll)
|
||||
responseGetAll.code should equal(403)
|
||||
responseGetAll.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanGetAllDynamicResourceDocs}")
|
||||
|
||||
|
||||
Then(s"we test the $ApiEndpoint4")
|
||||
|
||||
val requestUpdate = (v4_0_0_Request / "management" / "dynamic-resource-docs" / "xx").PUT <@ (user1)
|
||||
val responseUpdate = makePutRequest(requestUpdate,write(postDynamicResourceDoc))
|
||||
|
||||
responseUpdate.code should equal(403)
|
||||
responseUpdate.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanUpdateDynamicResourceDoc}")
|
||||
|
||||
Then(s"we test the $ApiEndpoint5")
|
||||
|
||||
val requestDelete = (v4_0_0_Request / "management" / "dynamic-resource-docs" / "xx").DELETE <@ (user1)
|
||||
val responseDelete = makeDeleteRequest(requestDelete)
|
||||
|
||||
responseDelete.code should equal(403)
|
||||
responseDelete.body.extract[ErrorMessage].message should equal(s"$UserHasMissingRoles${CanDeleteDynamicResourceDoc}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -204,7 +204,17 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop
|
||||
returnValue should be (Full(OBPDescending))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
implicit val fromDateOrdering = new Ordering[OBPFromDate] {
|
||||
override def compare(x: OBPFromDate, y: OBPFromDate): Int = if (x.value.after(y.value)) {
|
||||
1
|
||||
} else if(y.value.after(x.value)) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
feature("test APIUtil.getFromDate method")
|
||||
{
|
||||
scenario(s"test the correct case")
|
||||
@ -222,21 +232,41 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop
|
||||
returnValue.toString contains FilterDateFormatError should be (true)
|
||||
}
|
||||
|
||||
scenario(s"test the wrong case: wrong name (wrongName) in HTTPParam")
|
||||
scenario("test the wrong case: wrong name (wrongName) in HTTPParam")
|
||||
{
|
||||
val httpParams: List[HTTPParam] = List(HTTPParam("wrongName", List(s"$DateWithMsExampleString")))
|
||||
val startTime = OBPFromDate(DefaultFromDate)
|
||||
val returnValue = getFromDate(httpParams)
|
||||
returnValue should be (OBPFromDate(DefaultFromDate))
|
||||
returnValue shouldBe a[Full[OBPFromDate]]
|
||||
|
||||
val currentTime = OBPFromDate(DefaultFromDate)
|
||||
val beWithinTolerance = be >= startTime and be <= currentTime
|
||||
returnValue.orNull should beWithinTolerance
|
||||
}
|
||||
|
||||
scenario(s"test the wrong case: wrong name (wrongName) and wrong values (wrongValue) in HTTPParam")
|
||||
scenario("test the wrong case: wrong name (wrongName) and wrong values (wrongValue) in HTTPParam")
|
||||
{
|
||||
val httpParams: List[HTTPParam] = List(HTTPParam("wrongName", List("wrongValue")))
|
||||
val startTime = OBPFromDate(DefaultFromDate)
|
||||
val returnValue = getFromDate(httpParams)
|
||||
returnValue should be (OBPFromDate(DefaultFromDate))
|
||||
returnValue shouldBe a[Full[OBPFromDate]]
|
||||
|
||||
val currentTime = OBPFromDate(DefaultFromDate)
|
||||
val beWithinTolerance = be >= startTime and be <= currentTime
|
||||
returnValue.orNull should beWithinTolerance
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
implicit val toDateOrdering = new Ordering[OBPToDate] {
|
||||
override def compare(x: OBPToDate, y: OBPToDate): Int = if (x.value.after(y.value)) {
|
||||
1
|
||||
} else if(y.value.after(x.value)) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
feature("test APIUtil.getToDate method")
|
||||
{
|
||||
scenario(s"test the correct case")
|
||||
@ -257,15 +287,29 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop
|
||||
scenario(s"test the wrong case: wrong name (wrongName) in HTTPParam")
|
||||
{
|
||||
val httpParams: List[HTTPParam] = List(HTTPParam("wrongName", List(s"$DateWithMsExampleString")))
|
||||
|
||||
val startTime = OBPToDate(DefaultToDate)
|
||||
|
||||
val returnValue = getToDate(httpParams)
|
||||
returnValue should be (OBPToDate(DefaultToDate))
|
||||
returnValue shouldBe a[Full[OBPToDate]]
|
||||
|
||||
val currentTime = OBPToDate(DefaultToDate)
|
||||
val beWithinTolerance = be >= startTime and be <= currentTime
|
||||
returnValue.orNull should beWithinTolerance
|
||||
}
|
||||
|
||||
scenario(s"test the wrong case: wrong name (wrongName) and wrong values (wrongValue) in HTTPParam")
|
||||
{
|
||||
val httpParams: List[HTTPParam] = List(HTTPParam("wrongName", List("wrongValue")))
|
||||
|
||||
val startTime = OBPToDate(DefaultToDate)
|
||||
|
||||
val returnValue = getToDate(httpParams)
|
||||
returnValue should be (OBPToDate(DefaultToDate))
|
||||
returnValue shouldBe a[Full[OBPToDate]]
|
||||
|
||||
val currentTime = OBPToDate(DefaultToDate)
|
||||
val beWithinTolerance = be >= startTime and be <= currentTime
|
||||
returnValue.orNull should beWithinTolerance
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
119
obp-api/src/test/scala/code/util/DynamicUtilTest.scala
Normal file
119
obp-api/src/test/scala/code/util/DynamicUtilTest.scala
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH.
|
||||
Osloer Strasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
|
||||
*/
|
||||
|
||||
package code.util
|
||||
|
||||
import code.api.util._
|
||||
import code.setup.PropsReset
|
||||
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.json
|
||||
import org.scalatest.{FeatureSpec, FlatSpec, GivenWhenThen, Matchers, Tag}
|
||||
|
||||
class DynamicUtilTest extends FlatSpec with Matchers {
|
||||
object DynamicUtilsTag extends Tag("DynamicUtil")
|
||||
|
||||
implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
|
||||
|
||||
"DynamicUtil.compileScalaCode method" should "return correct function" taggedAs DynamicUtilsTag in {
|
||||
val functionBody: Box[Int => Int] = DynamicUtil.compileScalaCode("def getBank(bankId : Int): Int = bankId+2; getBank _")
|
||||
|
||||
val getBankResponse = functionBody.openOrThrowException("")(123)
|
||||
|
||||
getBankResponse should be (125)
|
||||
}
|
||||
|
||||
|
||||
val zson = {
|
||||
"""
|
||||
|{
|
||||
| "name": "Sam",
|
||||
| "age": [12],
|
||||
| "isMarried": true,
|
||||
| "weight": 12.11,
|
||||
| "class": "2",
|
||||
| "def": 12,
|
||||
| "email": ["abc@def.com", "hijk@abc.com"],
|
||||
| "address": [{
|
||||
| "name": "jieji",
|
||||
| "code": 123123,
|
||||
| "street":{"road": "gongbin", "number": 123},
|
||||
| "_optional_fields_": ["code"]
|
||||
| }],
|
||||
| "street": {"name": "hongqi", "width": 12.11},
|
||||
| "_optional_fields_": ["age", "weight", "address"]
|
||||
|}
|
||||
|""".stripMargin
|
||||
}
|
||||
val zson2 = """{"road": "gongbin", "number": 123}"""
|
||||
val zson3 = """[{"road": "gongbin", "number": 123}]"""
|
||||
|
||||
def buildFunction(jsonStr: String): String => Any = {
|
||||
val caseClasses = JsonUtils.toCaseClasses(json.parse(jsonStr))
|
||||
|
||||
val code =
|
||||
s"""
|
||||
| $caseClasses
|
||||
|
|
||||
| // throws exception: net.liftweb.json.MappingException:
|
||||
| //No usable value for name
|
||||
| //Did not find value which can be converted into java.lang.String
|
||||
|
|
||||
|implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
|(str: String) => {
|
||||
| net.liftweb.json.parse(str).extract[RootJsonClass]
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val fun: Box[String => Any] = DynamicUtil.compileScalaCode(code)
|
||||
fun.orNull
|
||||
}
|
||||
|
||||
"Parse json to dynamic case object" should "success" taggedAs DynamicUtilsTag in {
|
||||
|
||||
val func = buildFunction(zson)
|
||||
val func2 = buildFunction(zson2)
|
||||
val func3 = buildFunction(zson3)
|
||||
val value1 = func.apply(zson)
|
||||
val value2 = func2.apply(zson2)
|
||||
val value3 = func2.apply(zson3)
|
||||
|
||||
|
||||
ReflectUtils.getNestedField(value1.asInstanceOf[AnyRef], "street", "name") should be ("hongqi")
|
||||
ReflectUtils.getField(value1.asInstanceOf[AnyRef], "weight") shouldEqual Some(12.11)
|
||||
ReflectUtils.getField(value2.asInstanceOf[AnyRef], "number") shouldEqual (123)
|
||||
ReflectUtils.getField(value3.asInstanceOf[AnyRef], "number") shouldEqual (123)
|
||||
}
|
||||
|
||||
"DynamicUtil.toCaseObject method" should "return correct object" taggedAs DynamicUtilsTag in {
|
||||
|
||||
val jValueZson2 = json.parse(zson2)
|
||||
val zson2Object: Product = DynamicUtil.toCaseObject(jValueZson2)
|
||||
zson2Object.isInstanceOf[Product] should be (true)
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
<groupId>com.tesobe</groupId>
|
||||
<artifactId>obp-parent</artifactId>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<version>1.8.2</version>
|
||||
<version>1.9.0</version>
|
||||
</parent>
|
||||
<artifactId>obp-commons</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -40,9 +40,15 @@ object JsonAble {
|
||||
def unapply(jsonAble: JsonAble)(implicit format: Formats): Option[JValue] = Option(jsonAble).map(_.toJValue)
|
||||
}
|
||||
|
||||
object JsonAbleSerializer extends Serializer[JsonAble] {
|
||||
trait ObpSerializer[T] extends Serializer[T] {
|
||||
override final def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), T] = Functions.doNothing
|
||||
}
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), JsonAble] = Functions.doNothing
|
||||
trait ObpDeSerializer[T] extends Serializer[T] {
|
||||
override final def serialize(implicit format: Formats): PartialFunction[Any, json.JValue] = Functions.doNothing
|
||||
}
|
||||
|
||||
object JsonAbleSerializer extends ObpSerializer[JsonAble] {
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
|
||||
case JsonAble(jValue) => jValue
|
||||
@ -70,7 +76,7 @@ object EnumValueSerializer extends Serializer[EnumValue] {
|
||||
* deSerialize trait or abstract type json, this Serializer should always put at formats chain first, e.g:
|
||||
* DefaultFormats + AbstractTypeDeserializer + ...others
|
||||
*/
|
||||
object AbstractTypeDeserializer extends Serializer[AnyRef] {
|
||||
object AbstractTypeDeserializer extends ObpDeSerializer[AnyRef] {
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), AnyRef] = {
|
||||
case (TypeInfo(clazz, _), json) if Modifier.isAbstract(clazz.getModifiers) && ReflectUtils.isObpClass(clazz) =>
|
||||
@ -79,12 +85,9 @@ object AbstractTypeDeserializer extends Serializer[AnyRef] {
|
||||
implicit val manifest = ManifestFactory.classType[AnyRef](commonClass)
|
||||
json.extract[AnyRef](format, manifest)
|
||||
}
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
|
||||
|
||||
}
|
||||
|
||||
object SimpleEnumDeserializer extends Serializer[SimpleEnum] {
|
||||
object SimpleEnumDeserializer extends ObpDeSerializer[SimpleEnum] {
|
||||
private val simpleEnumClazz = classOf[SimpleEnum]
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), SimpleEnum] = {
|
||||
case (TypeInfo(clazz, _), json) if simpleEnumClazz.isAssignableFrom(clazz) =>
|
||||
@ -94,8 +97,6 @@ object SimpleEnumDeserializer extends Serializer[SimpleEnum] {
|
||||
.asInstanceOf[SimpleEnumCollection[SimpleEnum]]
|
||||
.valueOf(enumValue)
|
||||
}
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
|
||||
}
|
||||
|
||||
object BigDecimalSerializer extends Serializer[BigDecimal] {
|
||||
@ -113,15 +114,13 @@ object BigDecimalSerializer extends Serializer[BigDecimal] {
|
||||
}
|
||||
}
|
||||
|
||||
object StringDeserializer extends Serializer[String] {
|
||||
object StringDeserializer extends ObpDeSerializer[String] {
|
||||
private val IntervalClass = classOf[String]
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), String] = {
|
||||
case (TypeInfo(IntervalClass, _), json) if !json.isInstanceOf[JString] =>
|
||||
compactRender(json)
|
||||
}
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,7 +254,7 @@ object FiledRenameSerializer extends Serializer[JsonFieldReName] {
|
||||
/**
|
||||
* make tolerate for missing required constructor parameters
|
||||
*/
|
||||
object JNothingSerializer extends Serializer[Any] {
|
||||
object JNothingSerializer extends ObpDeSerializer[Any] {
|
||||
|
||||
// This field is just a tag to declare all the missing fields are added, to avoid check missing field repeatedly
|
||||
val addedMissingFields = "addedMissingFieldsThisFieldIsJustBeTag"
|
||||
@ -289,9 +288,6 @@ object JNothingSerializer extends Serializer[Any] {
|
||||
}
|
||||
}
|
||||
|
||||
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
|
||||
|
||||
|
||||
private[this] def unapply(arg: (TypeInfo, JValue))(implicit formats: Formats): Option[(TypeInfo, JValue, Map[String, Class[_]])] = {
|
||||
val (TypeInfo(clazz, _), jValue) = arg
|
||||
if (! ReflectUtils.isObpClass(clazz) || !jValue.isInstanceOf[JObject] || jValue == JNothing || jValue == JNull || isNoMissingFields(jValue)) {
|
||||
@ -413,7 +409,7 @@ object ListResultSerializer extends Serializer[ListResult[_]] {
|
||||
/**
|
||||
* serialize DB Mapped object to JValue
|
||||
*/
|
||||
object MapperSerializer extends Serializer[Mapper[_]] {
|
||||
object MapperSerializer extends ObpSerializer[Mapper[_]] {
|
||||
/**
|
||||
* `call by name` method names those defined in Mapper trait.
|
||||
*/
|
||||
@ -437,9 +433,6 @@ object MapperSerializer extends Serializer[Mapper[_]] {
|
||||
}).toMap
|
||||
json.Extraction.decompose(map)
|
||||
}
|
||||
|
||||
|
||||
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, json.JValue), Mapper[_]] = Functions.doNothing
|
||||
}
|
||||
|
||||
@scala.annotation.meta.field
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package com.openbankproject.commons.util
|
||||
|
||||
import java.util.{Date, Objects}
|
||||
|
||||
import com.openbankproject.commons.util.Functions.Implicits._
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.json.JsonParser.ParseException
|
||||
import net.liftweb.json.{Diff, JNothing, JNull}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import net.liftweb.json.{Diff, JDouble, JInt, JNothing, JNull, JString}
|
||||
import org.apache.commons.lang3.{StringUtils, Validate}
|
||||
|
||||
import java.util.Objects
|
||||
import scala.reflect.runtime.universe
|
||||
import scala.reflect.runtime.universe.typeOf
|
||||
|
||||
@ -247,6 +247,43 @@ object JsonUtils {
|
||||
if (f.isDefinedAt(field, path)) f(field, path) else field
|
||||
}
|
||||
|
||||
/**
|
||||
* peek all nested fields, call callback function for every field
|
||||
* @param jValue
|
||||
* @param f
|
||||
* @return Unit
|
||||
*/
|
||||
def peekField(jValue: JValue)(f: (JField, String) => Unit): Unit = {
|
||||
def buildPath(parentPath: String, currentFieldName: String): String =
|
||||
if(parentPath == "") currentFieldName else s"$parentPath.$currentFieldName"
|
||||
|
||||
def rec(v: JValue, path: String): Unit = v match {
|
||||
case JObject(l) => l.foreach { field =>
|
||||
rec(field.value, buildPath(path, field.name))
|
||||
f(field, path)
|
||||
}
|
||||
|
||||
case JArray(l) => l.foreach(rec(_, path))
|
||||
case v => v // do nothing, just placeholder
|
||||
}
|
||||
rec(jValue, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* according predicate function to collect all fulfill fields
|
||||
* @param jValue
|
||||
* @param predicate
|
||||
* @return JField to field path
|
||||
*/
|
||||
def collectField(jValue: JValue)(predicate: (JField, String) => Boolean): List[(JField, String)] = {
|
||||
val fields = scala.collection.mutable.ListBuffer[(JField, String)]()
|
||||
peekField(jValue) { (field, path) =>
|
||||
if(predicate(field, path))
|
||||
fields += field -> path
|
||||
}
|
||||
fields.toList
|
||||
}
|
||||
|
||||
/**
|
||||
* enhance JValue, to support operations: !,+,-*,/,&,|
|
||||
* @param jValue
|
||||
@ -612,5 +649,209 @@ object JsonUtils {
|
||||
buffer -= "$outer" // removed the nest class references
|
||||
buffer.toMap
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* is jValue type of JBool|JString|JDouble|JInt
|
||||
* @param jValue
|
||||
* @return true if jValue is type of JBool|JString|JDouble|JInt
|
||||
*/
|
||||
def isBasicType(jValue: JValue) = jValue match {
|
||||
case JBool(_) | JString(_) | JDouble(_) | JInt(_) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
// scala reserved word mapping escaped string: "class" -> "`class`"
|
||||
private val reservedToEscaped =
|
||||
List("abstract", "case", "catch", "class", "def", "do", "else", "extends", "false",
|
||||
"final", "finally", "for", "forSome", "if", "implicit", "import", "lazy", "match",
|
||||
"new", "null", "object", "override", "package", "private", "protected", "return",
|
||||
"sealed", "super", "this", "throw", "trait", "try", "true", "type", "val", "var",
|
||||
"while", "with", "yield")
|
||||
.toMapByValue(it => s"`$it`")
|
||||
|
||||
private val optionalFieldName = "_optional_fields_"
|
||||
|
||||
// if Option[Boolean], Option[Double], Option[Long] will lost type argument when do deserialize with lift-json: Option[Object]
|
||||
// this will make generated scala code can't extract json to this object, So use java.lang.Xxx for these type in Option.
|
||||
private def toScalaTypeName(jValue: JValue, isOptional: Boolean = false) = jValue match {
|
||||
case _: JBool if isOptional => "Option[java.lang.Boolean]"
|
||||
case _: JBool => "Boolean"
|
||||
case _: JDouble if isOptional => "Option[java.lang.Double]"
|
||||
case _: JDouble => "Double"
|
||||
case _: JInt if isOptional => "Option[java.lang.Long]"
|
||||
case _: JInt => "Long"
|
||||
case _: JString if isOptional => "Option[String]"
|
||||
case _: JString => "String"
|
||||
case _: JObject if isOptional => "Option[AnyRef]"
|
||||
case _: JObject => "AnyRef"
|
||||
case _: JArray if isOptional => "Option[List[Any]]"
|
||||
case _: JArray => "List[Any]"
|
||||
case null | JNull | JNothing => throw new IllegalArgumentException(s"Json value must not be null")
|
||||
}
|
||||
|
||||
/**
|
||||
* validate any nested array type field have the same structure, and not empty, and have not null item
|
||||
* @param void
|
||||
*/
|
||||
private def validateJArray(jvalue: JValue): Unit = {
|
||||
if(jvalue.isInstanceOf[JArray]) {
|
||||
val rootJson: JObject = "" -> jvalue
|
||||
validateJArray(rootJson)
|
||||
} else {
|
||||
peekField(jvalue) {
|
||||
case (jField @ JField(fieldName, JArray(arr)), path) =>
|
||||
val fullFieldName = if(StringUtils.isBlank(path)) fieldName else s"$path.$fieldName"
|
||||
// not empty and none item is null
|
||||
Validate.isTrue(arr.nonEmpty, s"Json $fullFieldName should not be empty array.")
|
||||
Validate.isTrue(arr.notExists(it => it == JNull || it == JNothing), s" Array json $fullFieldName should not contains null item.")
|
||||
if(arr.size > 1) {
|
||||
arr match {
|
||||
case JBool(_) :: tail => Validate.isTrue(tail.forall(_.isInstanceOf[JBool]), s"All the items of Json $fullFieldName should be Boolean type.")
|
||||
case JString(_) :: tail => Validate.isTrue(tail.forall(_.isInstanceOf[JString]), s"All the items of Json $fullFieldName should be String type.")
|
||||
case JDouble(_) :: tail => Validate.isTrue(tail.forall(_.isInstanceOf[JDouble]), s"All the items of Json $fullFieldName should be number type.")
|
||||
case JInt(_) :: tail => Validate.isTrue(tail.forall(_.isInstanceOf[JInt]), s"All the items of Json $fullFieldName should be integer type.")
|
||||
case (head: JObject) :: tail =>
|
||||
Validate.isTrue(tail.forall(_.isInstanceOf[JObject]), s"All the items of Json $fullFieldName should be object type.")
|
||||
def fieldNameToType(jObject: JObject) = jObject.obj.map(it => it.name -> getType(it.value)).toMap
|
||||
val headFieldNameToType = fieldNameToType(head)
|
||||
val allItemsHaveSameStructure = tail.map(it => fieldNameToType(it.asInstanceOf[JObject])).forall(headFieldNameToType ==)
|
||||
Validate.isTrue(allItemsHaveSameStructure, s"All the items of Json $fullFieldName should the same structure.")
|
||||
case JArray(_) :: tail => Validate.isTrue(tail.forall(_.isInstanceOf[JArray]), s"All the items of Json $fullFieldName should be array type.")
|
||||
}
|
||||
}
|
||||
case v => v // do nothing, just as place holder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getNestedJObjects(jObject: JObject, typeNamePrefix: String): List[String] = {
|
||||
val nestedObjects = collectField(jObject) {
|
||||
case (JField(_, _: JObject), _) => true
|
||||
case (JField(_, JArray((_: JObject) :: _)), _) => true
|
||||
case (JField(_, JArray((_: JArray) :: _)), _) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def getParentFiledName(path: String) = path match {
|
||||
case v if v.contains('.') =>
|
||||
StringUtils.substringAfterLast(path, ".")
|
||||
case v => v
|
||||
}
|
||||
|
||||
val subTypes: List[String] = nestedObjects collect {
|
||||
case (JField(name, v: JObject), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(name, JArray((v: JObject) :: _)), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(name, JArray(JArray((v: JObject) :: _) :: _)), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(name, JArray(JArray(JArray((v: JObject) :: _) :: _) :: _)), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(name, JArray(JArray(JArray(JArray((v: JObject) :: _) :: _) :: _) :: _)), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(name, JArray(JArray(JArray(JArray(JArray((v: JObject) :: _) :: _) :: _) :: _) :: _)), path) =>
|
||||
jObjectToCaseClass(v, typeNamePrefix, name, getParentFiledName(path))
|
||||
case (JField(_, JArray(JArray(JArray(JArray(JArray(JArray(_ :: _) :: _) :: _) :: _) :: _) :: _)), path) =>
|
||||
throw new IllegalArgumentException(s"Json field $path have too much nested level, max nested level be supported is 5.")
|
||||
} toList
|
||||
|
||||
subTypes
|
||||
}
|
||||
|
||||
/**
|
||||
* generate case class string according json structure
|
||||
* @param jvalue
|
||||
* @return case class string
|
||||
*/
|
||||
def toCaseClasses(jvalue: JValue, typeNamePrefix: String = ""): String = {
|
||||
validateJArray(jvalue)
|
||||
jvalue match {
|
||||
case _: JBool => s"type ${typeNamePrefix}RootJsonClass = Boolean"
|
||||
case _: JString => s"type ${typeNamePrefix}RootJsonClass = String"
|
||||
case _: JDouble => s"type ${typeNamePrefix}RootJsonClass = Double"
|
||||
case _: JInt => s"type ${typeNamePrefix}RootJsonClass = Long"
|
||||
case jObject: JObject =>
|
||||
validateJArray(jObject)
|
||||
val allDefinitions = getNestedJObjects(jObject, typeNamePrefix) :+ jObjectToCaseClass(jObject, typeNamePrefix)
|
||||
allDefinitions mkString "\n"
|
||||
|
||||
case jArray: JArray =>
|
||||
validateJArray(jvalue)
|
||||
|
||||
def buildArrayType(jArray: JArray):String = jArray.arr.head match {
|
||||
case _: JBool => "List[Boolean]"
|
||||
case _: JString => "List[String]"
|
||||
case _: JDouble => "List[Double]"
|
||||
case _: JInt => "List[Long]"
|
||||
case v: JObject =>
|
||||
val itemsType = jObjectToCaseClass(v, typeNamePrefix, "RootItem")
|
||||
s"""$itemsType
|
||||
|List[${typeNamePrefix}RootItemJsonClass]
|
||||
|""".stripMargin
|
||||
case v: JArray =>
|
||||
val nestedItmType = buildArrayType(v)
|
||||
// replace the last row to List[Xxx], e.g:
|
||||
/*
|
||||
case class Foo(i:Int)
|
||||
Foo
|
||||
-->
|
||||
case class Foo(i:Int)
|
||||
List[Foo]
|
||||
*/
|
||||
nestedItmType.replaceAll("(^|.*\\s+)(.+)\\s*$", "$1List[$2]")
|
||||
}
|
||||
// add type alias for last row
|
||||
buildArrayType(jArray).replaceAll("(^|.*\\s+)(.+)\\s*$", s"$$1 type ${typeNamePrefix}RootJsonClass = $$2")
|
||||
|
||||
case null | JNull | JNothing => throw new IllegalArgumentException(s"null value json can't generate case class")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private def jObjectToCaseClass(jObject: JObject, typeNamePrefix: String, fieldName: String = "", parentFieldName: String = ""): String = {
|
||||
val JObject(fields) = jObject
|
||||
val optionalFields = (jObject \ optionalFieldName) match {
|
||||
case JArray(arr) if arr.forall(_.isInstanceOf[JString]) =>
|
||||
arr.map(_.asInstanceOf[JString].s).toSet
|
||||
case JNull | JNothing => Set.empty[String]
|
||||
case _ => throw new IllegalArgumentException(s"Filed $optionalFieldName of $fieldName should be an array of String")
|
||||
}
|
||||
|
||||
def toCaseClassName(name: String) = s"$typeNamePrefix${fieldName.capitalize}${name.capitalize}JsonClass"
|
||||
|
||||
val currentCaseClass: String = fields collect {
|
||||
case JField(name, v) if isBasicType(v) =>
|
||||
val escapedFieldsName = reservedToEscaped.getOrElse(name, name)
|
||||
val fieldType = toScalaTypeName(v, optionalFields.contains(name))
|
||||
s"$escapedFieldsName: $fieldType"
|
||||
|
||||
case JField(name, _: JObject) =>
|
||||
val escapedFieldsName = reservedToEscaped.getOrElse(name, name)
|
||||
val fieldType = if (optionalFields.contains(name)) s"Option[${toCaseClassName(name)}]" else toCaseClassName(name)
|
||||
s"$escapedFieldsName: $fieldType"
|
||||
|
||||
case JField(name, arr: JArray) if name != optionalFieldName =>
|
||||
val isOption: Boolean = optionalFields.contains(name)
|
||||
def buildArrayType(jArray: JArray): String = jArray.arr.head match {
|
||||
case _: JBool => "List[java.lang.Boolean]"
|
||||
case _: JDouble => "List[java.lang.Double]"
|
||||
case _: JInt => "List[java.lang.Long]"
|
||||
case _: JString => "List[String]"
|
||||
case _: JObject => s"List[${toCaseClassName(name)}]"
|
||||
|
||||
case v: JArray =>
|
||||
val nestedItmType = buildArrayType(v)
|
||||
s"List[$nestedItmType]"
|
||||
}
|
||||
|
||||
val fieldType = if (isOption) s"Option[${buildArrayType(arr)}]" else buildArrayType(arr)
|
||||
|
||||
val escapedFieldsName = reservedToEscaped.getOrElse(name, name)
|
||||
s"$escapedFieldsName: $fieldType"
|
||||
} mkString(s"case class $typeNamePrefix${parentFieldName.capitalize}${if(fieldName.isEmpty) "Root" else fieldName.capitalize}JsonClass(", ", ", ")")
|
||||
|
||||
currentCaseClass
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +113,20 @@ object ReflectUtils {
|
||||
* @return the field value of obj
|
||||
*/
|
||||
def getField(obj: AnyRef, fieldName: String): Any = operateField[Any](obj, fieldName)(Functions.doNothingFn)
|
||||
/**
|
||||
* get given object nested field value
|
||||
* @param obj
|
||||
* @param fieldName field name
|
||||
* @return the field value of obj
|
||||
*/
|
||||
def getNestedField(obj: AnyRef, rootField: String, nestedFields: String*): Any = {
|
||||
nestedFields.foldLeft(getField(obj, rootField)) { (parentObject, field) =>
|
||||
assert(parentObject != null, s"Can't read `$field` value from null.")
|
||||
assert(parentObject.isInstanceOf[AnyRef], s"Value $parentObject must be AnyRef type.")
|
||||
|
||||
getField(parentObject.asInstanceOf[AnyRef], field)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* according object name get corresponding field value
|
||||
@ -618,12 +632,14 @@ object ReflectUtils {
|
||||
tp.typeSymbol.isClass && !tp.typeSymbol.asClass.isTrait match {
|
||||
case false => Map.empty[String, ru.Type]
|
||||
case true => {
|
||||
getPrimaryConstructor(tp)
|
||||
import scala.collection.immutable.ListMap
|
||||
val paramNameToTypeList = getPrimaryConstructor(tp)
|
||||
.paramLists
|
||||
.headOption
|
||||
.getOrElse(Nil)
|
||||
.map(it => (it.name.toString, it.info))
|
||||
.toMap
|
||||
|
||||
ListMap(paramNameToTypeList:_*)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
package com.openbankproject.commons.util
|
||||
|
||||
import java.util.Date
|
||||
import net.liftweb.json
|
||||
|
||||
import java.util.Date
|
||||
import net.liftweb.json.Extraction.decompose
|
||||
import net.liftweb.json.Formats
|
||||
import org.scalatest.{FlatSpec, Matchers, Tag}
|
||||
|
||||
class JsonUtilsTest extends FlatSpec with Matchers {
|
||||
object FunctionsTag extends Tag("JsonUtils")
|
||||
implicit def formats: Formats = net.liftweb.json.DefaultFormats
|
||||
|
||||
"collectFieldNames" should "return all the field names and path" taggedAs FunctionsTag in {
|
||||
|
||||
@ -39,5 +41,115 @@ class JsonUtilsTest extends FlatSpec with Matchers {
|
||||
names should contain ("nestField")
|
||||
names should not contain ("nestField1")
|
||||
}
|
||||
|
||||
|
||||
def toCaseClass(str: String, typeNamePrefix: String = ""): String = JsonUtils.toCaseClasses(json.parse(str), typeNamePrefix)
|
||||
|
||||
"object json String" should "generate correct case class" taggedAs FunctionsTag in {
|
||||
|
||||
val zson = {
|
||||
"""
|
||||
|{
|
||||
| "name": "Sam",
|
||||
| "age": 12,
|
||||
| "isMarried": true,
|
||||
| "weight": 12.11,
|
||||
| "class": "2",
|
||||
| "def": 12,
|
||||
| "email": ["abc@def.com", "hijk@abc.com"],
|
||||
| "address": [{
|
||||
| "name": "jieji",
|
||||
| "code": 123123,
|
||||
| "street":{"road": "gongbin", "number": 123}
|
||||
| }],
|
||||
| "street": {"name": "hongqi", "width": 12.11},
|
||||
| "_optional_fields_": ["age", "weight", "address"]
|
||||
|}
|
||||
|""".stripMargin
|
||||
}
|
||||
{
|
||||
val expectedCaseClass =
|
||||
"""case class AddressStreetJsonClass(road: String, number: Long)
|
||||
|case class AddressJsonClass(name: String, code: Long, street: AddressStreetJsonClass)
|
||||
|case class StreetJsonClass(name: String, width: Double)
|
||||
|case class RootJsonClass(name: String, age: Option[java.lang.Long], isMarried: Boolean, weight: Option[java.lang.Double], `class`: String, `def`: Long, email: List[String], address: Option[List[AddressJsonClass]], street: StreetJsonClass)""".stripMargin
|
||||
|
||||
val generatedCaseClass = toCaseClass(zson)
|
||||
|
||||
generatedCaseClass should be(expectedCaseClass)
|
||||
}
|
||||
{// test type name prefix
|
||||
val expectedCaseClass =
|
||||
"""case class RequestAddressStreetJsonClass(road: String, number: Long)
|
||||
|case class RequestAddressJsonClass(name: String, code: Long, street: RequestAddressStreetJsonClass)
|
||||
|case class RequestStreetJsonClass(name: String, width: Double)
|
||||
|case class RequestRootJsonClass(name: String, age: Option[java.lang.Long], isMarried: Boolean, weight: Option[java.lang.Double], `class`: String, `def`: Long, email: List[String], address: Option[List[RequestAddressJsonClass]], street: RequestStreetJsonClass)""".stripMargin
|
||||
|
||||
val generatedCaseClass = toCaseClass(zson, "Request")
|
||||
generatedCaseClass should be(expectedCaseClass)
|
||||
}
|
||||
}
|
||||
|
||||
"List json" should "generate correct case class" taggedAs FunctionsTag in {
|
||||
{
|
||||
val listIntJson = """[1,2,3]"""
|
||||
|
||||
toCaseClass(listIntJson) should be(""" type RootJsonClass = List[Long]""")
|
||||
toCaseClass(listIntJson, "Response") should be(""" type ResponseRootJsonClass = List[Long]""")
|
||||
}
|
||||
{
|
||||
val listObjectJson =
|
||||
"""[
|
||||
| {
|
||||
| "name": "zs"
|
||||
| "weight": 12.34
|
||||
| },
|
||||
| {
|
||||
| "name": "ls"
|
||||
| "weight": 21.43
|
||||
| }
|
||||
|]""".stripMargin
|
||||
val expectedCaseClass = """case class RootItemJsonClass(name: String, weight: Double)
|
||||
| type RootJsonClass = List[RootItemJsonClass]""".stripMargin
|
||||
|
||||
val expectedRequestCaseClass = """case class RequestRootItemJsonClass(name: String, weight: Double)
|
||||
| type RequestRootJsonClass = List[RequestRootItemJsonClass]""".stripMargin
|
||||
|
||||
|
||||
toCaseClass(listObjectJson) should be(expectedCaseClass)
|
||||
toCaseClass(listObjectJson, "Request") should be(expectedRequestCaseClass)
|
||||
}
|
||||
}
|
||||
|
||||
"List json have different type items" should "throw exception" taggedAs FunctionsTag in {
|
||||
|
||||
val listJson = """["abc",2,3]"""
|
||||
val listJson2 =
|
||||
"""[
|
||||
| {
|
||||
| "name": "zs"
|
||||
| "weight": 12.34
|
||||
| },
|
||||
| {
|
||||
| "name": "ls"
|
||||
| "weight": 21
|
||||
| }
|
||||
|]""".stripMargin
|
||||
val objectJson =
|
||||
"""{
|
||||
| "emails": [true, "abc@def.com"]
|
||||
|}""".stripMargin
|
||||
|
||||
val objectNestedListJson =
|
||||
"""{
|
||||
| "emails": {
|
||||
| "list": [12.34, "abc@def.com"]
|
||||
| }
|
||||
|}""".stripMargin
|
||||
|
||||
the [IllegalArgumentException] thrownBy toCaseClass(listJson) should have message "All the items of Json should be String type."
|
||||
the [IllegalArgumentException] thrownBy toCaseClass(listJson2) should have message "All the items of Json should the same structure."
|
||||
the [IllegalArgumentException] thrownBy toCaseClass(objectJson) should have message "All the items of Json emails should be Boolean type."
|
||||
the [IllegalArgumentException] thrownBy toCaseClass(objectNestedListJson) should have message "All the items of Json emails.list should be number type."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user