diff --git a/obp-api/pom.xml b/obp-api/pom.xml
index 40d044754..b3861b82e 100644
--- a/obp-api/pom.xml
+++ b/obp-api/pom.xml
@@ -8,7 +8,7 @@
com.tesobe
obp-parent
../pom.xml
- 1.8.2
+ 1.9.0
obp-api
war
diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
index 7e5792baf..26858e265 100644
--- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
+++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
@@ -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
diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala
index 893aa4b9e..e7d51e5da 100644
--- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala
+++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala
@@ -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))
}
}
diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
index 116ab55da..7752ae1fd 100644
--- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
+++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
@@ -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()
diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala
index 97ae92a60..f4d680580 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -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
diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala
index 2b23510c7..76d6217ab 100644
--- a/obp-api/src/main/scala/code/api/util/ApiRole.scala
+++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala
@@ -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]
diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala
index 44bc3df5c..f5d1077c3 100644
--- a/obp-api/src/main/scala/code/api/util/ApiTag.scala
+++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala
@@ -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")
diff --git a/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala b/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala
index 4211004dc..1bf30a24f 100644
--- a/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala
+++ b/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala
@@ -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)
+ }
}
\ No newline at end of file
diff --git a/obp-api/src/main/scala/code/api/util/DynamicUtil.scala b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala
new file mode 100644
index 000000000..a8888a2ad
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala
@@ -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
+}
diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
index bdd33317e..c4f1fd3f7 100644
--- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
+++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
@@ -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."
diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala
index 98ad20a95..a28c0e761 100644
--- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala
+++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala
@@ -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)
//------------------------------------------------------------
diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala
index d71b426c9..9c546927d 100644
--- a/obp-api/src/main/scala/code/api/util/NewStyle.scala
+++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala
@@ -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)
+ }
+
}
}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
index 5b28726bd..a28edad56 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
@@ -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))
+ }
+ }
+ }
+
}
}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
index 77f3ca77e..d7d1e284b 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
@@ -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 = {
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala
index 1c889d31c..034bf0664 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala
@@ -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.")
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicCompileEndpoint.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicCompileEndpoint.scala
new file mode 100644
index 000000000..f8e080abf
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicCompileEndpoint.scala
@@ -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)
+ }
+}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala
similarity index 99%
rename from obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala
rename to obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala
index 7b6fe5d80..6e0c1f7eb 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala
@@ -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
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpoints.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpoints.scala
new file mode 100644
index 000000000..719b4d17f
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpoints.scala
@@ -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)
+ }
+ }
+ }
+}
+
+
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala
similarity index 98%
rename from obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala
rename to obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala
index 60be8b9c1..c9668c350 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala
@@ -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._
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicResourceDocsEndpointGroup.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicResourceDocsEndpointGroup.scala
new file mode 100644
index 000000000..58f661faf
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicResourceDocsEndpointGroup.scala
@@ -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
+ }
+ )
+ }
+}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/DynamicEndpointCodeGenerator.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/DynamicEndpointCodeGenerator.scala
new file mode 100644
index 000000000..daa11f468
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/DynamicEndpointCodeGenerator.scala
@@ -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)
+ }
+}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpoint.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpoint.scala
new file mode 100644
index 000000000..f9331a067
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpoint.scala
@@ -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))
+ }
+ }
+
+}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpointGroup.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpointGroup.scala
new file mode 100644
index 000000000..7f809393e
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/practise/PractiseEndpointGroup.scala
@@ -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
+}
diff --git a/obp-api/src/main/scala/code/bankconnectors/DynamicConnector.scala b/obp-api/src/main/scala/code/bankconnectors/DynamicConnector.scala
new file mode 100644
index 000000000..848c230b0
--- /dev/null
+++ b/obp-api/src/main/scala/code/bankconnectors/DynamicConnector.scala
@@ -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)
+ }
+
+}
+
diff --git a/obp-api/src/main/scala/code/bankconnectors/InternalConnector.scala b/obp-api/src/main/scala/code/bankconnectors/InternalConnector.scala
index ce8974005..a99890224 100644
--- a/obp-api/src/main/scala/code/bankconnectors/InternalConnector.scala
+++ b/obp-api/src/main/scala/code/bankconnectors/InternalConnector.scala
@@ -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
-
}
diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala
index 54c6f21ba..963d08c2e 100644
--- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala
+++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala
@@ -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
diff --git a/obp-api/src/main/scala/code/connectormethod/ConnectorMethodProvider.scala b/obp-api/src/main/scala/code/connectormethod/ConnectorMethodProvider.scala
index 94daa2a65..083804a9f 100644
--- a/obp-api/src/main/scala/code/connectormethod/ConnectorMethodProvider.scala
+++ b/obp-api/src/main/scala/code/connectormethod/ConnectorMethodProvider.scala
@@ -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")
}
diff --git a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala
index 1a6163680..d0b451dfe 100644
--- a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala
+++ b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala
@@ -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.")
}
diff --git a/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDoc.scala b/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDoc.scala
new file mode 100644
index 000000000..fb1c286c9
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDoc.scala
@@ -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,
+ )
+}
\ No newline at end of file
diff --git a/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDocProvider.scala b/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDocProvider.scala
new file mode 100644
index 000000000..1685a1030
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicMessageDoc/DynamicMessageDocProvider.scala
@@ -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]
+
+}
\ No newline at end of file
diff --git a/obp-api/src/main/scala/code/dynamicMessageDoc/MappedDynamicMessageDocProvider.scala b/obp-api/src/main/scala/code/dynamicMessageDoc/MappedDynamicMessageDocProvider.scala
new file mode 100644
index 000000000..75201b499
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicMessageDoc/MappedDynamicMessageDocProvider.scala
@@ -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))
+ }
+}
\ No newline at end of file
diff --git a/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala
new file mode 100644
index 000000000..c4ace87c6
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala
@@ -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
+ )
+}
+
diff --git a/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDocProvider.scala b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDocProvider.scala
new file mode 100644
index 000000000..51b28ad76
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDocProvider.scala
@@ -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]
+
+}
diff --git a/obp-api/src/main/scala/code/dynamicResourceDoc/MappedDynamicResourceDocProvider.scala b/obp-api/src/main/scala/code/dynamicResourceDoc/MappedDynamicResourceDocProvider.scala
new file mode 100644
index 000000000..15f6222d2
--- /dev/null
+++ b/obp-api/src/main/scala/code/dynamicResourceDoc/MappedDynamicResourceDocProvider.scala
@@ -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))
+ }
+}
+
+
diff --git a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala
index 44bcc97c9..133bf203a 100644
--- a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala
+++ b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala
@@ -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._
diff --git a/obp-api/src/test/scala/RunMTLSWebApp.scala b/obp-api/src/test/scala/RunMTLSWebApp.scala
index c9e3346c4..60a331dd5 100644
--- a/obp-api/src/test/scala/RunMTLSWebApp.scala
+++ b/obp-api/src/test/scala/RunMTLSWebApp.scala
@@ -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]),
diff --git a/obp-api/src/test/scala/RunWebApp.scala b/obp-api/src/test/scala/RunWebApp.scala
index 44ab74734..791c1ceb3 100644
--- a/obp-api/src/test/scala/RunWebApp.scala
+++ b/obp-api/src/test/scala/RunWebApp.scala
@@ -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.
diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala
index c30674b8c..82a62495f 100644
--- a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala
+++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala
@@ -1,4 +1,4 @@
-package code.api.v3_1_0
+package code.api.ResourceDocs1_4_0
import java.util
diff --git a/obp-api/src/test/scala/code/api/v4_0_0/ConnectorMethodTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/ConnectorMethodTest.scala
index f4697a684..07fe6adfd 100644
--- a/obp-api/src/test/scala/code/api/v4_0_0/ConnectorMethodTest.scala
+++ b/obp-api/src/test/scala/code/api/v4_0_0/ConnectorMethodTest.scala
@@ -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")
diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicMessageDocTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicMessageDocTest.scala
new file mode 100644
index 000000000..f398716df
--- /dev/null
+++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicMessageDocTest.scala
@@ -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 .
+
+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}")
+ }
+ }
+
+
+}
diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicResourceDocTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicResourceDocTest.scala
new file mode 100644
index 000000000..e16fbe89e
--- /dev/null
+++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicResourceDocTest.scala
@@ -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 .
+
+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}")
+ }
+ }
+
+
+}
diff --git a/obp-api/src/test/scala/code/util/APIUtilTest.scala b/obp-api/src/test/scala/code/util/APIUtilTest.scala
index d92df992b..a3666cc5d 100644
--- a/obp-api/src/test/scala/code/util/APIUtilTest.scala
+++ b/obp-api/src/test/scala/code/util/APIUtilTest.scala
@@ -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
}
}
diff --git a/obp-api/src/test/scala/code/util/DynamicUtilTest.scala b/obp-api/src/test/scala/code/util/DynamicUtilTest.scala
new file mode 100644
index 000000000..c9673d1f2
--- /dev/null
+++ b/obp-api/src/test/scala/code/util/DynamicUtilTest.scala
@@ -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 .
+
+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)
+ }
+}
\ No newline at end of file
diff --git a/obp-commons/pom.xml b/obp-commons/pom.xml
index 18ddce6c1..ac08617c6 100644
--- a/obp-commons/pom.xml
+++ b/obp-commons/pom.xml
@@ -7,7 +7,7 @@
com.tesobe
obp-parent
../pom.xml
- 1.8.2
+ 1.9.0
obp-commons
jar
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala
index 169fe3c8b..e47056df2 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala
@@ -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
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonUtils.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonUtils.scala
index f03a60d16..38b4a3074 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonUtils.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonUtils.scala
@@ -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
+ }
}
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/ReflectUtils.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/ReflectUtils.scala
index 32878b473..bda7df656 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/util/ReflectUtils.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/ReflectUtils.scala
@@ -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:_*)
}
}
/**
diff --git a/obp-commons/src/test/scala/com/openbankproject/commons/util/JsonUtilsTest.scala b/obp-commons/src/test/scala/com/openbankproject/commons/util/JsonUtilsTest.scala
index 8f6bf246d..34adc8682 100644
--- a/obp-commons/src/test/scala/com/openbankproject/commons/util/JsonUtilsTest.scala
+++ b/obp-commons/src/test/scala/com/openbankproject/commons/util/JsonUtilsTest.scala
@@ -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."
+ }
+
}
diff --git a/pom.xml b/pom.xml
index a07a6fb68..3a2c1681e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.tesobe
obp-parent
- 1.8.2
+ 1.9.0
pom
Open Bank Project API Parent
2011