feature/persist_DynaicEntity_to_DB

This commit is contained in:
shuang 2019-10-25 10:57:29 +08:00
parent fcee7bd010
commit 49aa1bbe98
11 changed files with 149 additions and 46 deletions

View File

@ -29,6 +29,7 @@ package bootstrap.liftweb
import java.io.{File, FileInputStream}
import java.util.{Locale, TimeZone}
import code.DynamicData.DynamicData
import code.accountapplication.MappedAccountApplication
import code.accountattribute.MappedAccountAttribute
import code.accountholders.MapperAccountHolders
@ -660,6 +661,7 @@ object ToSchemify {
WebUiProps,
Authorisation,
DynamicEntity,
DynamicData,
AccountIdMapping,
)++ APIBuilder_Connector.allAPIBuilderModels
}

View File

@ -40,6 +40,7 @@ object ErrorMessages {
val DynamicEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing."
val EntityNotFoundByEntityId = "OBP-09005: Entity not found. Please specify a valid value for entityId."
val DynamicEntityOperationNotAllowed = "OBP-09006: Operation is not allowed, because Current DynamicEntity have upload data, must to delete all the data before this operation."
// General messages (OBP-10XXX)
val InvalidJsonFormat = "OBP-10001: Incorrect json format."

View File

@ -1452,7 +1452,7 @@ object NewStyle {
def getDynamicEntityById(dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404)
Future{
(dynamicEntity, callContext)
}

View File

@ -5,7 +5,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil._
import code.api.util.ApiRole._
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound}
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound, DynamicEntityOperationNotAllowed}
import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample}
import code.api.util.NewStyle.HttpCode
import code.api.util._
@ -808,6 +808,13 @@ trait APIMethods400 {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext)
// Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity.
(entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
(isExists, _) <- NewStyle.function.invokeDynamicConnector(IS_EXISTS_DATA, entity.entityName, None, None, callContext)
_ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed) {
isExists.isDefined && isExists.contains(JBool(false))
}
jsonObject = json.asInstanceOf[JObject]
dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId))
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext)
@ -848,6 +855,12 @@ trait APIMethods400 {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canDeleteDynamicEntity, callContext)
// Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity.
(entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
(isExists, _) <- NewStyle.function.invokeDynamicConnector(IS_EXISTS_DATA, entity.entityName, None, None, callContext)
_ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed) {
isExists.isDefined && isExists.contains(JBool(false))
}
deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId)
} yield {
(deleted, HttpCode.`200`(callContext))

View File

@ -2,9 +2,8 @@ package code.api.v4_0_0
import code.api.util.APIUtil.{Catalogs, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2}
import code.api.util.ApiTag.{apiTagApi, apiTagNewStyle}
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidUrl, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.{ApiTag, ApiVersion, NewStyle}
import net.liftweb.common.Box
import net.liftweb.json.JsonDSL._
import net.liftweb.json._
import net.liftweb.util.StringHelpers
@ -30,31 +29,6 @@ object MockerConnector {
def definitionsMap = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName))).toMap
//(id, entityName) -> entity
val persistedEntities = scala.collection.mutable.Map[(String, String), JObject]()
def persist(entityName: String, requestBody: JObject, id: Option[String] = None) = {
val idValue = id.orElse(Some(generateUUID()))
val idName = StringUtils.uncapitalize(entityName) + "Id"
val entityToPersist = this.definitionsMap(entityName).toResponse(requestBody, id)
val haveIdEntity = (entityToPersist \ idName) match {
case JNothing => JObject(JField(idName, JString(idValue.get)) :: entityToPersist.obj)
case _ => entityToPersist
}
persistedEntities.put((idValue.get, entityName), haveIdEntity)
haveIdEntity
}
def getSingle(entityName: String, id: String) = {
persistedEntities.get(id, entityName)
}
def getAll(entityName: String) = persistedEntities.filter(pair => pair._1._2 == entityName).values
def delete(entityName: String, id: String): Box[Boolean] = {
persistedEntities.remove(id -> entityName).map(_ => true)
}
def doc = {
val docs: Seq[ResourceDoc] = definitionsMap.values.flatMap(createDocs).toSeq
collection.mutable.ArrayBuffer(docs:_*)

View File

@ -56,7 +56,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint
// if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc.
val allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs,
def allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs,
Implementations4_0_0.resourceDocs,
MockerConnector.doc)
// all endpoints
@ -68,6 +68,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
// Filter the possible endpoints by the disabled / enabled Props settings and add them together
val routes : List[OBPEndpoint] =
Implementations4_0_0.genericEndpoint::
Implementations4_0_0.root :: // For now we make this mandatory
getAllowedEndpoints(endpoints, allResourceDocs)

View File

@ -3,6 +3,7 @@ package code.bankconnectors
import java.util.Date
import java.util.UUID.randomUUID
import code.DynamicData.DynamicDataProvider
import code.accountapplication.AccountApplicationX
import code.accountattribute.AccountAttributeX
import code.accountholders.AccountHolders
@ -10,10 +11,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.cache.Caching
import code.api.util.APIUtil.{OBPReturnType, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull}
import code.api.util.ErrorMessages._
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
import code.api.util._
import code.api.v4_0_0.MockerConnector
import code.api.v4_0_0.MockerConnector.persistedEntities
import code.atms.Atms.Atm
import code.atms.MappedAtm
import code.branches.Branches.Branch
@ -54,12 +52,14 @@ import code.views.Views
import com.google.common.cache.CacheBuilder
import com.nexmo.client.NexmoClient
import com.nexmo.client.sms.messages.TextMessage
import com.openbankproject.commons.model.enums.DynamicEntityOperation.{CREATE, DELETE, GET_ALL, GET_ONE, UPDATE}
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType, StrongCustomerAuthentication}
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
import com.openbankproject.commons.model.enums._
import com.openbankproject.commons.model.{AccountApplication, AccountAttribute, Product, ProductAttribute, ProductCollectionItem, TaxResidence, _}
import com.tesobe.CacheKeyFromArguments
import com.tesobe.model.UpdateBankAccount
import net.liftweb.common._
import net.liftweb.json
import net.liftweb.json.{JArray, JBool, JObject, JValue}
import net.liftweb.mapper.{By, _}
import net.liftweb.util.Helpers.{tryo, _}
@ -2819,31 +2819,41 @@ object LocalMappedConnector extends Connector with MdcLoggable {
return Helper.booleanToFuture(s"$InvalidJsonFormat entityId is required for $operation operation.")(entityId.isEmpty || StringUtils.isBlank(entityId.get))
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
}
if (!persistedEntities.contains(entityId.get -> entityName)) {
val id = entityId.get
val id = entityId.get
val value = DynamicDataProvider.connectorMethodProvider.vend.get(entityName, id)
if (value.isEmpty) {
return Helper.booleanToFuture(s"$EntityNotFoundByEntityId please check: entityId = $id", 404)(false)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
}
}
val op: Any = operation
Future {
val processResult: Box[JValue] = operation match {
val processResult: Box[JValue] = op match {
case GET_ALL => Full {
JArray(MockerConnector.getAll(entityName).toList)
val dataList = DynamicDataProvider.connectorMethodProvider.vend.getAll(entityName)
JArray(dataList)
}
case GET_ONE => {
val boxedEntity: Box[JValue] = MockerConnector.getSingle(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required.")))
val boxedEntity: Box[JValue] = DynamicDataProvider.connectorMethodProvider.vend
.get(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required.")))
.map(it => json.parse(it.dataJson))
boxedEntity
}
case CREATE | UPDATE => {
val body = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
val id = if(operation == CREATE) None else entityId
val persistedEntity = MockerConnector.persist(entityName, body, id)
Full(persistedEntity)
val boxedEntity: Box[JValue] = DynamicDataProvider.connectorMethodProvider.vend.saveOrUpdate(entityName, body, id)
.map(it => json.parse(it.dataJson))
boxedEntity
}
case DELETE => {
val deleteResult = MockerConnector.delete(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required. ")))
deleteResult.map(JBool(_))
val id = entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required. "))
val deleteResult: Boolean = DynamicDataProvider.connectorMethodProvider.vend.delete(entityName, id)
Full(JBool(deleteResult))
}
case IS_EXISTS_DATA => {
val isExistsData: Boolean = DynamicDataProvider.connectorMethodProvider.vend.existsData(entityName)
Full(JBool(isExistsData))
}
}
(processResult, callContext)

View File

@ -0,0 +1,41 @@
package code.DynamicData
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.json.JObject
import net.liftweb.util.SimpleInjector
object DynamicDataProvider extends SimpleInjector {
val connectorMethodProvider = new Inject(buildOne _) {}
def buildOne: MappedDynamicDataProvider.type = MappedDynamicDataProvider
}
trait DynamicDataT {
def dynamicDataId: Option[String]
def dynamicEntityName: String
def dataJson: String
}
case class DynamicDataCommons(dynamicEntityName: String,
dataJson: String,
dynamicDataId: Option[String] = None
) extends DynamicDataT with JsonFieldReName
object DynamicDataCommons extends Converter[DynamicDataT, DynamicDataCommons]
trait DynamicDataProvider {
def saveOrUpdate(entityName: String, requestBody: JObject, id: Option[String] = None): Box[DynamicData]
def get(entityName: String, id: String): Box[DynamicData]
def getAll(entityName: String): List[JObject]
def delete(entityName: String, id: String): Boolean
def existsData(dynamicEntityName: String): Boolean
}

View File

@ -0,0 +1,60 @@
package code.DynamicData
import code.api.util.APIUtil.generateUUID
import code.api.util.CustomJsonFormats
import code.util.MappedUUID
import net.liftweb.common.Box
import net.liftweb.json
import net.liftweb.json.JObject
import net.liftweb.json.JsonDSL._
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
object MappedDynamicDataProvider extends DynamicDataProvider with CustomJsonFormats{
override def saveOrUpdate(entityName: String, requestBody: JObject, id: Option[String]): Box[DynamicData] = {
val idValue: String = id.getOrElse(generateUUID())
val dynamicData: DynamicData = id match{
case Some(i) => get(entityName, i).openOrThrowException(s"not exists DynamicData's data of dynamicEntityName=$entityName, dynameicDataId=$i")
case _ => DynamicData.create.DynamicDataId(idValue).DynamicEntityName(entityName)
}
val idName = StringUtils.uncapitalize(entityName) + "Id"
val dataToPersist: JObject = requestBody ~ (idName -> idValue)
val dataStr = json.compactRender(dataToPersist)
tryo {
dynamicData.DataJson(dataStr).saveMe()
}
}
override def get(entityName: String, id: String): Box[DynamicData] = DynamicData.find(By(DynamicData.DynamicDataId, id))
override def getAll(entityName: String): List[JObject] = DynamicData.findAll(By(DynamicData.DynamicEntityName, entityName))
.map(it => json.parse(it.dataJson)).map(_.asInstanceOf[JObject])
override def delete(entityName: String, id: String): Boolean = DynamicData.bulkDelete_!!(By(DynamicData.DynamicDataId, id))
override def existsData(dynamicEntityName: String): Boolean = {
DynamicData.findAll(By(DynamicData.DynamicEntityName, dynamicEntityName), MaxRows(1))
.nonEmpty
}
}
class DynamicData extends DynamicDataT with LongKeyedMapper[DynamicData] with IdPK {
override def getSingleton = DynamicData
object DynamicDataId extends MappedUUID(this)
object DynamicEntityName extends MappedString(this, 255)
object DataJson extends MappedText(this)
override def dynamicDataId: Option[String] = Option(DynamicDataId.get)
override def dynamicEntityName: String = DynamicEntityName.get
override def dataJson: String = DataJson.get
}
object DynamicData extends DynamicData with LongKeyedMetaMapper[DynamicData] {
override def dbIndexes = UniqueIndex(DynamicDataId) :: super.dbIndexes
}

View File

@ -202,7 +202,7 @@ class DynamicEntityTest extends V400ServerSetup {
// update a not exists DynamicEntity
val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1)
val response404 = makePutRequest(request404, compactRender(updateRequest))
Then("We should get a 400")
Then("We should get a 404")
response404.code should equal(404)
response404.body.extract[ErrorMessage].message should startWith (DynamicEntityNotFoundByDynamicEntityId)
}

View File

@ -74,4 +74,5 @@ object DynamicEntityOperation extends OBPEnumeration[DynamicEntityOperation] {
object CREATE extends Value
object UPDATE extends Value
object DELETE extends Value
object IS_EXISTS_DATA extends Value
}