dynamic_entity_refactor_simplify: DynamicEntity operation move to connector

This commit is contained in:
shuang 2019-09-18 07:11:04 +08:00
parent d074db63b9
commit f4966d53cb
8 changed files with 178 additions and 41 deletions

View File

@ -36,6 +36,9 @@ object ErrorMessages {
// DynamicEntity Exceptions (OBP-09XXX)
val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id."
val DynamicEntityEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
val DynamicEntityEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing."
// General messages (OBP-10XXX)
val InvalidJsonFormat = "OBP-10001: Incorrect json format."

View File

@ -28,11 +28,14 @@ import code.util.Helper
import code.views.Views
import code.webhook.AccountWebhook
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, ProductAttributeType}
import com.openbankproject.commons.model.enums.DynamicEntityOperation.{CREATE, UPDATE}
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
import com.openbankproject.commons.model.{AccountApplication, Bank, Customer, CustomerAddress, Product, ProductCollection, ProductCollectionItem, TaxResidence, UserAuthContext, UserAuthContextUpdate, _}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.http.provider.HTTPParam
import net.liftweb.json
import net.liftweb.json.{JArray, JBool, JDouble, JInt, JObject, JString, JValue}
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
@ -1397,8 +1400,25 @@ object NewStyle {
}
}
def createOrUpdateDynamicEntity(dynamicEntity: DynamicEntityT): Future[Box[DynamicEntityT]] = Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
def createOrUpdateDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = {
val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName)
val isEntityNameNotChange = existsDynamicEntity.isEmpty ||
existsDynamicEntity.filter(_.dynamicEntityId == dynamicEntity.dynamicEntityId).isDefined
isEntityNameNotChange match {
case true => Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
case false => {
val entityNameExists = existsDynamicEntity.isDefined
// validate whether entityName is exists
val errorMsg = s"$DynamicEntityEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
Helper.booleanToFuture(errorMsg)(!entityNameExists).map { _ =>
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
}
}
}
def deleteDynamicEntity(dynamicEntityId: String): Future[Box[Boolean]] = Future {
@ -1413,6 +1433,11 @@ object NewStyle {
}
}
def getDynamicEntityByEntityName(entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future {
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
(boxedDynamicEntity, callContext)
}
private[this] val dynamicEntityTTL = APIUtil.getPropsValue(s"dynamicEntity.cache.ttl.seconds", "0").toInt
def getDynamicEntities(): List[DynamicEntityT] = {
@ -1451,6 +1476,50 @@ object NewStyle {
i => (unboxFullOrFail(i._1, callContext, s"$CreateTransactionsException"), i._2)
}
def invokeDynamicConnector(operation: DynamicEntityOperation, entityName: String, requestBody: Option[JObject], entityId: Option[String], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityEntityNotExists)
if(operation == CREATE || operation == UPDATE) {
val jsonTypeMap = Map[String, Class[_]](
("boolean", classOf[JBool]),
("string", classOf[JString]),
("array", classOf[JArray]),
("integer", classOf[JInt]),
("number", classOf[JDouble]),
)
val definitionJson = json.parse(dynamicEntity.metadataJson).asInstanceOf[JObject]
val entity = (definitionJson \ entityName).asInstanceOf[JObject]
val requiredFieldNames: Set[String] = (entity \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s).toSet
val fieldNameToTypeName: Map[String, String] = (entity \ "properties")
.asInstanceOf[JObject]
.obj
.map(field => (field.name, (field.value \ "type").asInstanceOf[JString].s))
.toMap
val fieldNameToType: Map[String, Class[_]] = fieldNameToTypeName
.mapValues(jsonTypeMap(_))
val bodyJson = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
val fields = bodyJson.obj.filter(it => fieldNameToType.keySet.contains(it.name))
// if there are field type are not match the definitions, there must be bug.
val invalidTypes = fields.filterNot(it => fieldNameToType(it.name).isInstance(it.value))
val invalidTypeNames = invalidTypes.map(_.name).mkString("[", ",", "]")
val missingRequiredFields = requiredFieldNames.filterNot(it => fields.exists(_.name == it))
val missingFieldNames = missingRequiredFields.mkString("[", ",", "]")
Helper.booleanToFuture(s"$InvalidJsonFormat these field type not correct: $invalidTypeNames")(invalidTypes.isEmpty).flatMap{ _ =>
Helper.booleanToFuture(s"$InvalidJsonFormat some required fields are missing: $missingFieldNames")(missingRequiredFields.isEmpty)
}.flatMap{ _ =>
Connector.connector.vend.dynamicEntityProcess(operation, entityName, requestBody, entityId, callContext)
}
} else {
Connector.connector.vend.dynamicEntityProcess(operation, entityName, requestBody, entityId, callContext)
}
}
}
}

View File

@ -25,6 +25,7 @@ import net.liftweb.http.rest.RestHelper
import net.liftweb.json.Serialization.write
import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample}
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
import com.openbankproject.commons.model.enums.DynamicEntityOperation.{CREATE, DELETE, GET_ALL, GET_ONE, UPDATE}
import net.liftweb.util.StringHelpers
import org.atteo.evo.inflector.English
import net.liftweb.json._
@ -763,11 +764,11 @@ trait APIMethods400 {
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
validateDynamicEntityJson(json)
val JField(name, _) = json.asInstanceOf[JObject].obj.head
DynamicEntityCommons(name, compactRender(json))
val JField(entityName, _) = json.asInstanceOf[JObject].obj.head
DynamicEntityCommons(entityName, compactRender(json))
}
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData)
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData, callContext)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
(commonsData.jValue, HttpCode.`201`(callContext))
@ -822,7 +823,7 @@ trait APIMethods400 {
(_, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData)
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData, callContext)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
(commonsData.jValue, HttpCode.`200`(callContext))
@ -869,41 +870,43 @@ trait APIMethods400 {
lazy val genericEndpoint: OBPEndpoint = {
case EntityName(entityName) :: Nil JsonGet req => {
cc =>
Future {
import net.liftweb.json.JsonDSL._
val listName = StringHelpers.snakify(English.plural(entityName))
val resultList = MockerConnector.getAll(entityName)
val jValue: JValue = listName -> resultList
(jValue, HttpCode.`200`(Some(cc)))
}
case EntityName(entityName) :: Nil JsonGet req => { cc =>
val listName = StringHelpers.snakify(English.plural(entityName))
for {
(Full(resultList: JObject), _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc))
} yield {
import net.liftweb.json.JsonDSL._
val jValue: JObject = listName -> resultList
(jValue, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName, id) JsonGet req => {
cc =>
Future {
(MockerConnector.getSingle(entityName, id), HttpCode.`200`(Some(cc)))
}
case EntityName(entityName, id) JsonGet req => {cc =>
for {
(Full(entity), _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), Some(cc))
} yield {
(entity, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName) :: Nil JsonPost json -> _ => {
cc =>
Future {
(MockerConnector.persist(entityName, json.asInstanceOf[JObject]), HttpCode.`201`(Some(cc)))
}
case EntityName(entityName) :: Nil JsonPost json -> _ => {cc =>
for {
(Full(entity), _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(json.asInstanceOf[JObject]), None, Some(cc))
} yield {
(entity, HttpCode.`201`(Some(cc)))
}
}
case EntityName(entityName, id) JsonPut json -> _ => {
cc =>
Future {
(MockerConnector.persist(entityName, json.asInstanceOf[JObject], Some(id)), HttpCode.`200`(Some(cc)))
}
case EntityName(entityName, id) JsonPut json -> _ => { cc =>
for {
(Full(entity), _) <- NewStyle.function.invokeDynamicConnector(UPDATE, entityName, Some(json.asInstanceOf[JObject]), Some(id), Some(cc))
} yield {
(entity, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName, id) JsonDelete req => {
cc =>
Future {
(MockerConnector.delete(entityName, id), HttpCode.`200`(Some(cc)))
}
case EntityName(entityName, id) JsonDelete req => { cc =>
for {
(Full(deleteResult), _) <- NewStyle.function.invokeDynamicConnector(DELETE, entityName, None, Some(id), Some(cc))
} yield {
(deleteResult, HttpCode.`200`(Some(cc)))
}
}
}

View File

@ -31,12 +31,12 @@ import code.transactionrequests.{TransactionRequestTypeCharge, TransactionReques
import code.users.Users
import code.util.Helper._
import code.views.Views
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, ProductAttributeType}
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
import com.openbankproject.commons.model.{AccountApplication, Bank, CounterpartyTrait, CustomerAddress, Product, ProductCollection, ProductCollectionItem, TaxResidence, TransactionRequestStatus, UserAuthContext, UserAuthContextUpdate, _}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.json.Extraction.decompose
import net.liftweb.json.JObject
import net.liftweb.json.JsonAST.JValue
import net.liftweb.mapper.By
import net.liftweb.util.Helpers.tryo
@ -1862,4 +1862,18 @@ trait Connector extends MdcLoggable with CustomJsonFormats{
chargePolicy: String,
callContext: Option[CallContext]): OBPReturnType[Box[TransactionId]] = Future{(Failure(setUnimplementedError), callContext)}
/**
* DynamicEntity process function
* @param operation type of operation, this is an enumeration
* @param entityName DynamicEntity's entity name
* @param requestBody content of request
* @param entityId id of given DynamicEntity
* @param callContext
* @return result DynamicEntity process
*/
def dynamicEntityProcess(operation: DynamicEntityOperation,
entityName: String,
requestBody: Option[JObject],
entityId: Option[String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future{(Failure(setUnimplementedError), callContext)}
}

View File

@ -12,6 +12,7 @@ import code.api.util.APIUtil.{OBPReturnType, isValidCurrencyISOCode, saveConnect
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.atms.Atms.Atm
import code.atms.MappedAtm
import code.branches.Branches.Branch
@ -51,11 +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.{AccountAttributeType, CardAttributeType, ProductAttributeType, StrongCustomerAuthentication}
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.{AccountApplication, AccountAttribute, Product, ProductAttribute, ProductCollectionItem, TaxResidence, _}
import com.tesobe.CacheKeyFromArguments
import com.tesobe.model.UpdateBankAccount
import net.liftweb.common._
import net.liftweb.json.JsonAST.{JArray, JBool}
import net.liftweb.json.{JObject, JValue}
import net.liftweb.mapper.{By, _}
import net.liftweb.util.Helpers.{tryo, _}
import net.liftweb.util.Mailer
@ -2785,4 +2789,29 @@ object LocalMappedConnector extends Connector with MdcLoggable {
(boxedData, callContext)
}
override def dynamicEntityProcess(operation: DynamicEntityOperation,
entityName: String,
requestBody: Option[JObject],
entityId: Option[String],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future {
val processResult: Box[JValue] = operation match {
case GET_ALL => Full {
JArray(MockerConnector.getAll(entityName).toList)
}
case GET_ONE => {
val boxedEntity: Box[JValue] = MockerConnector.getSingle(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required.")))
boxedEntity
}
case CREATE | UPDATE => {
val body = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
val persistedEntity = MockerConnector.persist(entityName, body, entityId)
Full(persistedEntity)
}
case DELETE => {
val deleteResult = MockerConnector.delete(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required. ")))
deleteResult.map(JBool(_))
}
}
(processResult, callContext)
}
}

View File

@ -52,6 +52,8 @@ object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommo
trait DynamicEntityProvider {
def getById(dynamicEntityId: String): Box[DynamicEntityT]
def getByEntityName(entityName: String): Box[DynamicEntityT]
def getDynamicEntities(): List[DynamicEntityT]
def createOrUpdate(dynamicEntity: DynamicEntityT): Box[DynamicEntityT]

View File

@ -13,6 +13,10 @@ object MappedDynamicEntityProvider extends DynamicEntityProvider with CustomJson
By(DynamicEntity.DynamicEntityId, dynamicEntityId)
)
override def getByEntityName(entityName: String): Box[DynamicEntityT] = DynamicEntity.find(
By(DynamicEntity.EntityName, entityName)
)
override def getDynamicEntities(): List[DynamicEntity] = {
DynamicEntity.findAll()
}

View File

@ -45,6 +45,7 @@ object PemCertificateRole extends OBPEnumeration[PemCertificateRole] {
object PSP_PI extends Value
}
//------api enumerations end ----
sealed trait DynamicEntityFieldType extends EnumValue
object DynamicEntityFieldType extends OBPEnumeration[DynamicEntityFieldType]{
object string extends Value
@ -53,4 +54,16 @@ object DynamicEntityFieldType extends OBPEnumeration[DynamicEntityFieldType]{
object boolean extends Value
// object array extends Value
// object `object` extends Value //TODO in the future, we consider support nested type
}
/**
* connector support operation type for DynamicEntity
*/
sealed trait DynamicEntityOperation extends EnumValue
object DynamicEntityOperation extends OBPEnumeration[DynamicEntityOperation] {
object GET_ALL extends Value
object GET_ONE extends Value
object CREATE extends Value
object UPDATE extends Value
object DELETE extends Value
}