Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Marko Milić 2021-03-01 09:22:24 +01:00
commit b8ec97ce1b
49 changed files with 3070 additions and 284 deletions

View File

@ -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>

View File

@ -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

View File

@ -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))
}
}

View File

@ -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()

View File

@ -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

View File

@ -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]

View File

@ -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")

View File

@ -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)
}
}

View 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
}

View File

@ -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."

View File

@ -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)
//------------------------------------------------------------

View File

@ -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)
}
}
}

View File

@ -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))
}
}
}
}
}

View File

@ -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 = {

View File

@ -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.")

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
}
}
}
}

View File

@ -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._

View File

@ -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
}
)
}
}

View File

@ -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)
}
}

View File

@ -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))
}
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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")
}

View File

@ -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.")
}

View File

@ -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,
)
}

View File

@ -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]
}

View File

@ -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))
}
}

View File

@ -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
)
}

View File

@ -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]
}

View File

@ -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))
}
}

View File

@ -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._

View File

@ -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]),

View File

@ -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.

View File

@ -1,4 +1,4 @@
package code.api.v3_1_0
package code.api.ResourceDocs1_4_0
import java.util

View File

@ -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")

View File

@ -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}")
}
}
}

View File

@ -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}")
}
}
}

View File

@ -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
}
}

View 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)
}
}

View File

@ -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>

View File

@ -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

View File

@ -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
}
}

View File

@ -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:_*)
}
}
/**

View File

@ -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."
}
}

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<version>1.8.2</version>
<version>1.9.0</version>
<packaging>pom</packaging>
<name>Open Bank Project API Parent</name>
<inceptionYear>2011</inceptionYear>