bugfix/trait_way_enum_wrong_serialize: move reusable Serializer to obp-commons, make trait way enum be sub type of SimpleEnum, Delete TransactionCommons type, it cause many wrong result when do serialize and deserialize.

This commit is contained in:
shuang 2020-06-29 17:03:03 +08:00
parent 19adf9f38e
commit b9b2dd89ab
17 changed files with 358 additions and 311 deletions

View File

@ -191,7 +191,7 @@ object MessageDocsSwaggerDefinitions
isBeneficiary = isBeneficiaryExample.value.toBoolean // True if the originAccount can send money to the Counterparty
)
val transactionCommons = TransactionCommons(
val transaction = Transaction(
`uuid`= transactionIdExample.value,
id = TransactionId(transactionIdExample.value),
thisAccount = bankAccountCommons,

View File

@ -8,16 +8,13 @@ import code.api.cache.Caching
import code.api.util.ApiRole.rolesMappedToClasses
import code.api.v3_1_0.ListResult
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.JsonFieldReName
import com.openbankproject.commons.util.{EnumValueSerializer, Functions, JsonAbleSerializer, ReflectUtils}
import com.openbankproject.commons.util._
import com.tesobe.CacheKeyFromArguments
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.{TypeInfo, compactRender, _}
import net.liftweb.util.StringHelpers
import net.liftweb.json.{TypeInfo, _}
import scala.concurrent.duration._
import scala.reflect.ManifestFactory
import scala.reflect.runtime.{universe => ru}
trait CustomJsonFormats {
implicit val formats: Formats = CustomJsonFormats.formats
@ -25,11 +22,11 @@ trait CustomJsonFormats {
object CustomJsonFormats {
val formats: Formats = net.liftweb.json.DefaultFormats + BigDecimalSerializer + StringSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
val formats: Formats = JsonSerializers.commonFormats + ListResultSerializer
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless + BigDecimalSerializer + StringSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless + ListResultSerializer ++ JsonSerializers.serializers
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) + BigDecimalSerializer + StringSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) + ListResultSerializer ++ JsonSerializers.serializers
implicit val nullTolerateFormats = formats + JNothingSerializer
@ -37,139 +34,7 @@ object CustomJsonFormats {
val dateFormat = net.liftweb.json.DefaultFormats.dateFormat
override val typeHints = ShortTypeHints(rolesMappedToClasses)
} + BigDecimalSerializer + StringSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
}
object StringSerializer extends Serializer[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
}
object BigDecimalSerializer extends Serializer[BigDecimal] {
private val IntervalClass = classOf[BigDecimal]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), BigDecimal] = {
case (TypeInfo(IntervalClass, _), json) => json match {
case JString(s) => BigDecimal(s)
case x => throw new MappingException("Can't convert " + x + " to BigDecimal")
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: BigDecimal => JString(x.toString())
}
}
object FiledRenameSerializer extends Serializer[JsonFieldReName] {
private val clazz = classOf[JsonFieldReName]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), JsonFieldReName] = {
case (typeInfo @ TypeInfo(entityType, _), json) if(isNeedRenameFieldNames(entityType, json))=> json match {
case JObject(fieldList) => {
val renamedJObject = APIUtil.camelifyMethod(json)
val optionalFields = getAnnotedFields(entityType, ru.typeOf[optional])
.map{
case (name, tp) if(tp <:< ru.typeOf[Long] || tp <:< ru.typeOf[Int] || tp <:< ru.typeOf[Short] || tp <:< ru.typeOf[Byte] || tp <:< ru.typeOf[Int]) => (name, JInt(0))
case (name, tp) if(tp <:< ru.typeOf[Double] || tp <:< ru.typeOf[Float]) => (name, JDouble(0))
case (name, tp) if(tp <:< ru.typeOf[Boolean]) => (name, JBool(false))
case (name, tp) => (name, JNull)
}
val addedNullValues: JValue = if(optionalFields.isEmpty) {
renamedJObject
} else {
val children = renamedJObject.asInstanceOf[JObject].obj
val childrenNames = children.map(_.name)
val nullFields = optionalFields.filter(pair => !children.contains(pair._1)).map(pair => JField(pair._1, pair._2)).toList
val jObject: JValue = JObject(children ++: nullFields)
jObject
}
val idFieldToIdValueName: Map[String, String] = getSomeIdFieldInfo(entityType)
val processedIdJObject = if(idFieldToIdValueName.isEmpty){
addedNullValues
} else {
addedNullValues.mapField {
case JField(name, jValue) if idFieldToIdValueName.contains(name) => JField(name, JObject(JField(idFieldToIdValueName(name), jValue)))
case jField => jField
}
}
Extraction.extract(processedIdJObject,typeInfo).asInstanceOf[JsonFieldReName]
}
case x => throw new MappingException("Can't convert " + x + " to JsonFieldReName")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: JsonFieldReName => {
val ignoreFieldNames = getObjAnnotedFields(x, ru.typeOf[ignore])
val renamedJFields = ReflectUtils.getConstructorArgs(x)
.filter(pair => !ignoreFieldNames.contains(pair._1))
.map(pair => {
val paramName = StringHelpers.snakify(pair._1)
val paramValue = pair._2
isSomeId(paramValue) match {
case false => JField(paramName, Extraction.decompose(paramValue))
case true => {
val idValue = ReflectUtils.getConstructorArgs(paramValue).head._2
JField(paramName, Extraction.decompose(idValue))
}
}
}) .toList
JObject(renamedJFields)
}
}
private[this] def isNeedRenameFieldNames(entityType: Class[_], jValue: JValue): Boolean = {
val isJsonFieldRename = clazz.isAssignableFrom(entityType)
isJsonFieldRename &&
jValue.isInstanceOf[JObject] &&
jValue.asInstanceOf[JObject].obj.exists(jField => StringHelpers.camelifyMethod(jField.name) != jField.name)
}
// check given object is some Id, only type name ends with "Id" and have a single param constructor
private def isSomeId(obj: Any) = obj match {
case null => false
case _ => obj.getClass.getSimpleName.endsWith("Id") && ReflectUtils.getPrimaryConstructor(obj).asMethod.paramLists.headOption.exists(_.size == 1)
}
private def isSomeIdType(tp: ru.Type) = tp.typeSymbol.name.toString.endsWith("Id") && ReflectUtils.getConstructorParamInfo(tp).size == 1
/**
* extract constructor params those type is some id, and return the field name to the id constructor value name
* for example:
* case class Foo(name: String, bankId: BankId(value:String))
* getSomeIdFieldInfo(typeOf[Foo]) == Map(("bankId" -> "value"))
* @param clazz to do extract class
* @return field name to id type single value name
*/
private def getSomeIdFieldInfo(clazz: Class[_]) = {
val paramNameToType: Map[String, ru.Type] = ReflectUtils.getConstructorInfo(clazz)
paramNameToType
.filter(nameToType => isSomeIdType(nameToType._2))
.map(nameToType => {
val (name, paramType) = nameToType
val singleParamName = ReflectUtils.getConstructorParamInfo(paramType).head._1
(name, singleParamName)
}
)
}
private def getAnnotedFields(clazz: Class[_], annotationType: ru.Type): Map[String, ru.Type] = {
val symbol = ReflectUtils.classToSymbol(clazz)
ReflectUtils.getPrimaryConstructor(symbol.toType)
.paramLists.headOption.getOrElse(Nil)
.filter(param => param.annotations.exists(_.tree.tpe <:< annotationType))
.map(it => (it.name.toString, it.info))
.toMap
}
private def getObjAnnotedFields(obj: Any, annotationType: ru.Type): Map[String, ru.Type] = getAnnotedFields(obj.getClass, annotationType)
} + ListResultSerializer ++ JsonSerializers.serializers
}
object ListResultSerializer extends Serializer[ListResult[_]] {
@ -321,12 +186,3 @@ object JNothingSerializer extends Serializer[Any] with MdcLoggable {
@scala.annotation.meta.field
@scala.annotation.meta.param
class ignore extends scala.annotation.StaticAnnotation
@scala.annotation.meta.field
@scala.annotation.meta.param
class optional extends scala.annotation.StaticAnnotation

View File

@ -5,7 +5,7 @@ import java.util.Date
import akka.pattern.ask
import code.actorsystem.ObpLookupSystem
import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions
import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions.{bankAccountCommons, bankCommons, transactionCommons, _}
import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions.{bankAccountCommons, bankCommons, transaction, _}
import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType, parseDate}
import code.api.util.ErrorMessages.{AdapterFunctionNotImplemented, AdapterUnknownError}
import code.api.util.ExampleValue._
@ -287,7 +287,7 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
InBoundGetTransactions(
inboundAdapterCallContext,
inboundStatus,
List(transactionCommons)
List(transaction)
)
),
adapterImplementation = Some(AdapterImplementation("Transactions", 10))
@ -321,7 +321,7 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
InBoundGetTransaction(
inboundAdapterCallContext,
inboundStatus,
transactionCommons
transaction
)
),
adapterImplementation = Some(AdapterImplementation("Transactions", 11))

View File

@ -10,7 +10,7 @@ import code.bankconnectors.LocalMappedConnector._
import code.model.dataAccess.MappedBank
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.dto._
import com.openbankproject.commons.model.{CreditLimit, _}
import com.openbankproject.commons.model.{CreditLimit, Transaction, _}
import net.liftweb.common.Box
import scala.collection.immutable.List
@ -154,8 +154,8 @@ object Transformer {
}
def toInternalTransaction(t: Transaction): TransactionCommons = {
TransactionCommons(
def toInternalTransaction(t: Transaction): Transaction = {
Transaction(
uuid = t.uuid ,
id = t.id ,
thisAccount = BankAccountCommons(

View File

@ -2218,7 +2218,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
status=inboundStatusMessageStatusExample.value,
errorCode=inboundStatusMessageErrorCodeExample.value,
text=inboundStatusMessageTextExample.value))),
data=List( TransactionCommons(uuid=transactionUuidExample.value,
data=List( Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2265,7 +2265,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
import com.openbankproject.commons.dto.{OutBoundGetTransactionsLegacy => OutBound, InBoundGetTransactionsLegacy => InBound}
val url = getUrl(callContext, "getTransactionsLegacy")
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull , bankId, accountID, OBPQueryParam.getLimit(queryParams), OBPQueryParam.getOffset(queryParams), OBPQueryParam.getFromDate(queryParams), OBPQueryParam.getToDate(queryParams))
val result: OBPReturnType[Box[List[TransactionCommons]]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
val result: OBPReturnType[Box[List[Transaction]]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
result
}
@ -2320,7 +2320,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
status=inboundStatusMessageStatusExample.value,
errorCode=inboundStatusMessageErrorCodeExample.value,
text=inboundStatusMessageTextExample.value))),
data=List( TransactionCommons(uuid=transactionUuidExample.value,
data=List( Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2367,7 +2367,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
import com.openbankproject.commons.dto.{OutBoundGetTransactions => OutBound, InBoundGetTransactions => InBound}
val url = getUrl(callContext, "getTransactions")
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull , bankId, accountID, OBPQueryParam.getLimit(queryParams), OBPQueryParam.getOffset(queryParams), OBPQueryParam.getFromDate(queryParams), OBPQueryParam.getToDate(queryParams))
val result: OBPReturnType[Box[List[TransactionCommons]]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
val result: OBPReturnType[Box[List[Transaction]]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
result
}
@ -2519,7 +2519,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
status=inboundStatusMessageStatusExample.value,
errorCode=inboundStatusMessageErrorCodeExample.value,
text=inboundStatusMessageTextExample.value))),
data= TransactionCommons(uuid=transactionUuidExample.value,
data= Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2566,7 +2566,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
import com.openbankproject.commons.dto.{OutBoundGetTransactionLegacy => OutBound, InBoundGetTransactionLegacy => InBound}
val url = getUrl(callContext, "getTransactionLegacy")
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull , bankId, accountID, transactionId)
val result: OBPReturnType[Box[TransactionCommons]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
val result: OBPReturnType[Box[Transaction]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
result
}
@ -2618,7 +2618,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
status=inboundStatusMessageStatusExample.value,
errorCode=inboundStatusMessageErrorCodeExample.value,
text=inboundStatusMessageTextExample.value))),
data= TransactionCommons(uuid=transactionUuidExample.value,
data= Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2665,7 +2665,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
import com.openbankproject.commons.dto.{OutBoundGetTransaction => OutBound, InBoundGetTransaction => InBound}
val url = getUrl(callContext, "getTransaction")
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull , bankId, accountID, transactionId)
val result: OBPReturnType[Box[TransactionCommons]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
val result: OBPReturnType[Box[Transaction]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext))
result
}

View File

@ -1,4 +1,4 @@
-- auto generated MS sql server procedures script, create on 2020-06-26T23:21:46Z
-- auto generated MS sql server procedures script, create on 2020-06-29T16:54:14Z
-- drop procedure obp_get_adapter_info
DROP PROCEDURE IF EXISTS obp_get_adapter_info;
@ -3240,6 +3240,10 @@ this is example of parameter @outbound_json
},
"data":[
{
"uuid":"Transaction uuid string",
"id":{
"value":"2fg8a7e4-6d02-40e3-a129-0b2bf89de8ub"
},
"thisAccount":{
"accountId":{
"value":"8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0"
@ -3272,8 +3276,6 @@ this is example of parameter @outbound_json
],
"accountHolder":"bankAccount accountHolder string"
},
"description":"For the piano lesson in June 2018 - Invoice No: 68",
"uuid":"Transaction uuid string",
"otherAccount":{
"nationalIdentifier":"Counterparty nationalIdentifier string",
"kind":"Counterparty kind string",
@ -3293,14 +3295,12 @@ this is example of parameter @outbound_json
"isBeneficiary":true
},
"transactionType":"DEBIT",
"balance":"50.89",
"amount":"19.64",
"id":{
"value":"2fg8a7e4-6d02-40e3-a129-0b2bf89de8ub"
},
"currency":"EUR",
"description":"For the piano lesson in June 2018 - Invoice No: 68",
"startDate":"2019-09-06T16:00:00Z",
"finishDate":"2019-09-07T16:00:00Z",
"startDate":"2019-09-06T16:00:00Z"
"balance":"50.89"
}
]
}'
@ -3610,6 +3610,10 @@ this is example of parameter @outbound_json
]
},
"data":{
"uuid":"Transaction uuid string",
"id":{
"value":"2fg8a7e4-6d02-40e3-a129-0b2bf89de8ub"
},
"thisAccount":{
"accountId":{
"value":"8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0"
@ -3642,8 +3646,6 @@ this is example of parameter @outbound_json
],
"accountHolder":"bankAccount accountHolder string"
},
"description":"For the piano lesson in June 2018 - Invoice No: 68",
"uuid":"Transaction uuid string",
"otherAccount":{
"nationalIdentifier":"Counterparty nationalIdentifier string",
"kind":"Counterparty kind string",
@ -3663,14 +3665,12 @@ this is example of parameter @outbound_json
"isBeneficiary":true
},
"transactionType":"DEBIT",
"balance":"50.89",
"amount":"19.64",
"id":{
"value":"2fg8a7e4-6d02-40e3-a129-0b2bf89de8ub"
},
"currency":"EUR",
"description":"For the piano lesson in June 2018 - Invoice No: 68",
"startDate":"2019-09-06T16:00:00Z",
"finishDate":"2019-09-07T16:00:00Z",
"startDate":"2019-09-06T16:00:00Z"
"balance":"50.89"
}
}'
);
@ -3806,7 +3806,7 @@ this is example of parameter @outbound_json
"string"
],
"allows":[
{}
"DEBIT"
],
"account":{
"accountId":{
@ -3842,12 +3842,12 @@ this is example of parameter @outbound_json
},
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{
@ -4133,7 +4133,7 @@ this is example of parameter @outbound_json
"string"
],
"allows":[
{}
"DEBIT"
],
"account":{
"accountId":{
@ -4169,12 +4169,12 @@ this is example of parameter @outbound_json
},
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{
@ -4291,12 +4291,12 @@ this is example of parameter @outbound_json
"bankId":"gh.29.uk",
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{
@ -4352,7 +4352,7 @@ this is example of parameter @outbound_json
"string"
],
"allows":[
{}
"DEBIT"
],
"account":{
"accountId":{
@ -4388,12 +4388,12 @@ this is example of parameter @outbound_json
},
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{
@ -4510,12 +4510,12 @@ this is example of parameter @outbound_json
"bankId":"gh.29.uk",
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{
@ -4571,7 +4571,7 @@ this is example of parameter @outbound_json
"string"
],
"allows":[
{}
"DEBIT"
],
"account":{
"accountId":{
@ -4607,12 +4607,12 @@ this is example of parameter @outbound_json
},
"replacement":{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FIRST"
},
"pinResets":[
{
"requestedDate":"2020-01-26T16:00:00Z",
"reasonRequested":{}
"reasonRequested":"FORGOT"
}
],
"collected":{

View File

@ -73,7 +73,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
val connectorName = "stored_procedure_vDec2019"
//---------------- dynamic start -------------------please don't modify this line
// ---------- created on 2020-06-26T23:17:54Z
// ---------- created on 2020-06-29T16:53:12Z
messageDocs += getAdapterInfoDoc
def getAdapterInfoDoc = MessageDoc(
@ -989,7 +989,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
exampleInboundMessage = (
InBoundGetTransactions(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
status=MessageDocsSwaggerDefinitions.inboundStatus,
data=List( TransactionCommons(uuid=transactionUuidExample.value,
data=List( Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -1036,7 +1036,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
import com.openbankproject.commons.dto.{OutBoundGetTransactions => OutBound, InBoundGetTransactions => InBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, accountID, OBPQueryParam.getLimit(queryParams), OBPQueryParam.getOffset(queryParams), OBPQueryParam.getFromDate(queryParams), OBPQueryParam.getToDate(queryParams))
val response: Future[Box[InBound]] = sendRequest[InBound]("obp_get_transactions", req, callContext)
response.map(convertToTuple[List[TransactionCommons]](callContext))
response.map(convertToTuple[List[Transaction]](callContext))
}
messageDocs += getTransactionsCoreDoc
@ -1122,7 +1122,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
exampleInboundMessage = (
InBoundGetTransaction(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
status=MessageDocsSwaggerDefinitions.inboundStatus,
data= TransactionCommons(uuid=transactionUuidExample.value,
data= Transaction(uuid=transactionUuidExample.value,
id=TransactionId(transactionIdExample.value),
thisAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -1169,7 +1169,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
import com.openbankproject.commons.dto.{OutBoundGetTransaction => OutBound, InBoundGetTransaction => InBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, accountID, transactionId)
val response: Future[Box[InBound]] = sendRequest[InBound]("obp_get_transaction", req, callContext)
response.map(convertToTuple[TransactionCommons](callContext))
response.map(convertToTuple[Transaction](callContext))
}
messageDocs += getPhysicalCardForBankDoc
@ -5648,8 +5648,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
response.map(convertToTuple[Boolean](callContext))
}
// ---------- created on 2020-06-26T23:17:54Z
//---------------- dynamic end ---------------------please don't modify this line
// ---------- created on 2020-06-29T16:53:12Z
//---------------- dynamic end ---------------------please don't modify this line
private val availableOperation = DynamicEntityOperation.values.map(it => s""""$it"""").mkString("[", ", ", "]")

View File

@ -2,8 +2,8 @@ package code.management
import java.util.Date
import code.api.util.{APIUtil, BigDecimalSerializer, CustomJsonFormats}
import code.api.util.ErrorMessages._
import code.api.util.{APIUtil, CustomJsonFormats}
import code.bankconnectors.Connector
import code.tesobe.ErrorMessage
import code.util.Helper.MdcLoggable
@ -12,8 +12,8 @@ import net.liftweb.common.Full
import net.liftweb.http._
import net.liftweb.http.js.JsExp
import net.liftweb.http.rest.RestHelper
import net.liftweb.json.Extraction
import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString}
import net.liftweb.json.{Extraction}
import net.liftweb.util.Helpers._
/**

View File

@ -26,6 +26,10 @@
<groupId>net.liftweb</groupId>
<artifactId>lift-common_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-util_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-reflect</artifactId>

View File

@ -134,14 +134,14 @@ case class OutBoundGetTransactions(outboundAdapterCallContext: OutboundAdapterCa
offset: Int,
fromDate: String,
toDate: String) extends TopicTrait
case class InBoundGetTransactions(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[TransactionCommons]) extends InBoundTrait[List[TransactionCommons]]
case class InBoundGetTransactions(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[Transaction]) extends InBoundTrait[List[Transaction]]
case class OutBoundGetTransaction(outboundAdapterCallContext: OutboundAdapterCallContext,
bankId: BankId,
accountId: AccountId,
transactionId: TransactionId) extends TopicTrait
case class InBoundGetTransaction(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: TransactionCommons) extends InBoundTrait[TransactionCommons]
case class InBoundGetTransaction(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: Transaction) extends InBoundTrait[Transaction]
case class OutBoundMakePaymentv210(outboundAdapterCallContext: OutboundAdapterCallContext,
fromAccount: BankAccountCommons,
@ -880,14 +880,14 @@ case class OutBoundGetTransactionsLegacy (outboundAdapterCallContext: OutboundAd
offset: Int,
fromDate: String,
toDate: String) extends TopicTrait
case class InBoundGetTransactionsLegacy (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[TransactionCommons]) extends InBoundTrait[List[TransactionCommons]]
case class InBoundGetTransactionsLegacy (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[Transaction]) extends InBoundTrait[List[Transaction]]
case class OutBoundGetTransactionLegacy (outboundAdapterCallContext: OutboundAdapterCallContext,
bankId: BankId,
accountID: AccountId,
transactionId: TransactionId) extends TopicTrait
case class InBoundGetTransactionLegacy (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: TransactionCommons) extends InBoundTrait[TransactionCommons]
case class InBoundGetTransactionLegacy (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: Transaction) extends InBoundTrait[Transaction]
case class OutBoundCreatePhysicalCardLegacy (outboundAdapterCallContext: OutboundAdapterCallContext,

View File

@ -665,7 +665,7 @@ case class TransactionRequestBody (
val description : String
)
class Transaction(
case class Transaction(
//A universally unique id
val uuid: String,
//id is unique for transactions of @thisAccount
@ -693,40 +693,6 @@ class Transaction(
case class UserCommons(userPrimaryKey : UserPrimaryKey, userId: String,idGivenByProvider: String, provider : String, emailAddress : String, name : String) extends User
// because Transaction#thisAccount is trait, can't be deserialize, So here supply a case class to do deserialize
case class TransactionCommons(
//A universally unique id
override val uuid: String,
override val id : TransactionId,
override val thisAccount : BankAccountCommons,
override val otherAccount : Counterparty,
override val transactionType : String,
override val amount : BigDecimal,
override val currency : String,
override val description : Option[String],
override val startDate : Date,
override val finishDate : Date,
override val balance : BigDecimal
) extends Transaction(uuid, id, thisAccount, otherAccount, transactionType, amount, currency,description, startDate, finishDate, balance) with JsonAble {
// if constructor override val value pass to parent class constructor, lift json will not work to do serialize, so here manually do serialize.
override def toJValue(implicit format: Formats): json.JValue = {
val map = Map(
"uuid" -> uuid,
"id" -> id,
"thisAccount" -> thisAccount,
"otherAccount" -> otherAccount,
"transactionType" -> transactionType,
"amount" -> amount,
"currency" -> currency,
"description" -> description,
"startDate" -> startDate,
"finishDate" -> finishDate,
"balance" -> balance,
)
json.Extraction.decompose(map)
}
}
case class InternalBasicUser(
userId:String,
emailAddress: String,

View File

@ -28,6 +28,10 @@ package com.openbankproject.commons.model
import java.util.Date
import com.openbankproject.commons.model.enums.{SimpleEnum, SimpleEnumCollection}
import scala.collection.immutable.ListMap
/**
* Represents a physical card (credit, debit, etc.)
*
@ -84,64 +88,59 @@ case class PhysicalCard (
) extends PhysicalCardTrait
sealed trait CardAction
sealed trait CardAction extends SimpleEnum
case object CardAction {
case object CardAction extends SimpleEnumCollection[CardAction] {
//TODO: are these good, or should they be changed (also, are there more actions to add?)
case object CREDIT extends CardAction
case object DEBIT extends CardAction
case object CASH_WITHDRAWAL extends CardAction
def valueOf(value: String) = value match {
case "credit" => CREDIT
case "debit" => DEBIT
case "cash_withdrawal" => CASH_WITHDRAWAL
case _ => throw new IllegalArgumentException ("Incorrect CardAction value: " + value)
}
val availableValues = "credit" :: "debit" :: "cash_withdrawal" :: Nil
override def nameToValue: Map[String, CardAction] = ListMap(
"credit"-> CREDIT,
"debit" -> DEBIT,
"cash_withdrawal" -> CASH_WITHDRAWAL
)
}
sealed trait Network
sealed trait Network extends SimpleEnum
//TODO: what kind of networks are there?
object Network extends SimpleEnumCollection[Network] {
override def nameToValue: Map[String, Network] = ListMap()
}
case class CardReplacementInfo(requestedDate : Date, reasonRequested: CardReplacementReason)
sealed trait CardReplacementReason
sealed trait CardReplacementReason extends SimpleEnum
case object CardReplacementReason {
case object CardReplacementReason extends SimpleEnumCollection[CardReplacementReason] {
case object LOST extends CardReplacementReason
case object STOLEN extends CardReplacementReason
case object RENEW extends CardReplacementReason
case object FIRST extends CardReplacementReason
def valueOf(value: String) = value match {
case "LOST" => LOST
case "STOLEN" => STOLEN
case "RENEW" => RENEW
case "FIRST" => FIRST
case _ => throw new IllegalArgumentException ("Incorrect CardReplacementReason value: " + value)
}
val availableValues = "LOST" :: "STOLEN" :: "RENEW" :: "FIRST" :: Nil
override def nameToValue: Map[String, CardReplacementReason] = ListMap(
"LOST" -> LOST,
"STOLEN" -> STOLEN,
"RENEW" -> RENEW,
"FIRST" -> FIRST
)
}
case class PinResetInfo(requestedDate: Date, reasonRequested: PinResetReason)
sealed trait PinResetReason
sealed trait PinResetReason extends SimpleEnum
case object PinResetReason {
case object PinResetReason extends SimpleEnumCollection[PinResetReason]{
case object FORGOT extends PinResetReason
case object GOOD_SECURITY_PRACTICE extends PinResetReason
def valueOf(value: String) = value match {
case "FORGOT" => FORGOT
case "GOOD_SECURITY_PRACTICE" => GOOD_SECURITY_PRACTICE
case _ => throw new IllegalArgumentException ("Incorrect PinResetReason value: " + value)
}
val availableValues = "FORGOT" :: "GOOD_SECURITY_PRACTICE" :: Nil
override def nameToValue: Map[String, PinResetReason] = ListMap(
"FORGOT" -> FORGOT,
"GOOD_SECURITY_PRACTICE" -> GOOD_SECURITY_PRACTICE
)
}

View File

@ -2,10 +2,10 @@ package com.openbankproject.commons.model.enums
import java.time.format.DateTimeFormatter
import com.openbankproject.commons.util.{EnumValue, OBPEnumeration}
import com.openbankproject.commons.util.{EnumValue, JsonAble, OBPEnumeration}
import net.liftweb.common.Box
import net.liftweb.json.JsonAST.JString
import net.liftweb.json.{JBool, JDouble, JInt, JValue}
import net.liftweb.json.{Formats, JBool, JDouble, JInt, JValue}
sealed trait AccountAttributeType extends EnumValue
object AccountAttributeType extends OBPEnumeration[AccountAttributeType]{
@ -135,4 +135,23 @@ object AttributeCategory extends OBPEnumeration[AttributeCategory]{
object TransactionRequestStatus extends Enumeration {
type TransactionRequestStatus = Value
val INITIATED, PENDING, NEXT_CHALLENGE_PENDING, FAILED, COMPLETED, FORWARDED, REJECTED = Value
}
}
//-------------------simple enum definition, just some sealed trait way, start-------------
trait SimpleEnum extends JsonAble {
override def toJValue(implicit format: Formats): JValue = {
val simpleName = this.getClass.getSimpleName.replaceFirst("\\$$", "")
JString(simpleName)
}
}
trait SimpleEnumCollection[+T] {
def nameToValue: Map[String, T]
def valueOf(value: String): T = nameToValue.get(value)
.getOrElse(throw new IllegalArgumentException ("Incorrect CardAction value: " + value))
val availableValues = nameToValue.keys.toList
}
//-------------------simple enum definition, just some sealed trait way, end-------------

View File

@ -1,34 +0,0 @@
package com.openbankproject.commons.util
import net.liftweb.json._
trait JsonAble {
def toJValue(implicit format: Formats): JValue
}
object JsonAble {
def unapply(jsonAble: JsonAble)(implicit format: Formats): Option[JValue] = Option(jsonAble).map(_.toJValue)
}
object JsonAbleSerializer extends Serializer[JsonAble] {
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), JsonAble] = Functions.doNothing
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case JsonAble(jValue) => jValue
}
}
object EnumValueSerializer extends Serializer[EnumValue] {
private val IntervalClass = classOf[EnumValue]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), EnumValue] = {
case (TypeInfo(clazz, _), json) if(IntervalClass.isAssignableFrom(clazz)) => json match {
case JString(s) => OBPEnumeration.withName(clazz.asInstanceOf[Class[EnumValue]], s)
case x => throw new MappingException(s"Can't convert $x to $clazz")
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: EnumValue => JString(x.toString())
}
}

View File

@ -0,0 +1,230 @@
package com.openbankproject.commons.util
import java.lang.reflect.Modifier
import com.openbankproject.commons.model.JsonFieldReName
import com.openbankproject.commons.model.enums.{SimpleEnum, SimpleEnumCollection}
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json._
import net.liftweb.util.StringHelpers
import scala.reflect.ManifestFactory
import scala.reflect.runtime.{universe => ru}
object JsonSerializers {
val serializers = AbstractTypeDeserializer :: SimpleEnumDeserializer ::
BigDecimalSerializer :: StringDeserializer :: FiledRenameSerializer ::
EnumValueSerializer :: JsonAbleSerializer :: Nil
implicit val commonFormats = net.liftweb.json.DefaultFormats ++ serializers
}
trait JsonAble {
def toJValue(implicit format: Formats): JValue
}
object JsonAble {
def unapply(jsonAble: JsonAble)(implicit format: Formats): Option[JValue] = Option(jsonAble).map(_.toJValue)
}
object JsonAbleSerializer extends Serializer[JsonAble] {
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), JsonAble] = Functions.doNothing
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case JsonAble(jValue) => jValue
}
}
object EnumValueSerializer extends Serializer[EnumValue] {
private val IntervalClass = classOf[EnumValue]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), EnumValue] = {
case (TypeInfo(clazz, _), json) if(IntervalClass.isAssignableFrom(clazz)) => json match {
case JString(s) => OBPEnumeration.withName(clazz.asInstanceOf[Class[EnumValue]], s)
case x => throw new MappingException(s"Can't convert $x to $clazz")
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: EnumValue => JString(x.toString())
}
}
/**
* 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] {
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), AnyRef] = {
case (TypeInfo(clazz, _), json) if Modifier.isAbstract(clazz.getModifiers) && ReflectUtils.isObpClass(clazz) =>
val commonClass = Class.forName(s"com.openbankproject.commons.model.${clazz.getSimpleName}Commons")
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] {
private val simpleEnumClazz = classOf[SimpleEnum]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), SimpleEnum] = {
case (TypeInfo(clazz, _), json) if simpleEnumClazz.isAssignableFrom(clazz) =>
val JString(enumValue) = json.asInstanceOf[JString]
ReflectUtils.getObject(clazz.getName) // get Companion instance
.asInstanceOf[SimpleEnumCollection[SimpleEnum]]
.valueOf(enumValue)
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Functions.doNothing
}
object BigDecimalSerializer extends Serializer[BigDecimal] {
private val IntervalClass = classOf[BigDecimal]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), BigDecimal] = {
case (TypeInfo(IntervalClass, _), json) => json match {
case JString(s) => BigDecimal(s)
case x => throw new MappingException("Can't convert " + x + " to BigDecimal")
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: BigDecimal => JString(x.toString())
}
}
object StringDeserializer extends Serializer[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
}
/**
* when do serialize, fields name to snakify,
* when do deserialize, fields name to camelify
*/
object FiledRenameSerializer extends Serializer[JsonFieldReName] {
private val clazz = classOf[JsonFieldReName]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), JsonFieldReName] = {
case (typeInfo @ TypeInfo(entityType, _), json) if(isNeedRenameFieldNames(entityType, json))=> json match {
case JObject(fieldList) => {
val renamedJObject = json.transformField {
case JField(name, value) => JField(StringHelpers.camelifyMethod(name), value)
}
val optionalFields = getAnnotedFields(entityType, ru.typeOf[optional])
.map{
case (name, tp) if(tp <:< ru.typeOf[Long] || tp <:< ru.typeOf[Int] || tp <:< ru.typeOf[Short] || tp <:< ru.typeOf[Byte] || tp <:< ru.typeOf[Int]) => (name, JInt(0))
case (name, tp) if(tp <:< ru.typeOf[Double] || tp <:< ru.typeOf[Float]) => (name, JDouble(0))
case (name, tp) if(tp <:< ru.typeOf[Boolean]) => (name, JBool(false))
case (name, tp) => (name, JNull)
}
val addedNullValues: JValue = if(optionalFields.isEmpty) {
renamedJObject
} else {
val children = renamedJObject.asInstanceOf[JObject].obj
val childrenNames = children.map(_.name)
val nullFields = optionalFields.filter(pair => !children.contains(pair._1)).map(pair => JField(pair._1, pair._2)).toList
val jObject: JValue = JObject(children ++: nullFields)
jObject
}
val idFieldToIdValueName: Map[String, String] = getSomeIdFieldInfo(entityType)
val processedIdJObject = if(idFieldToIdValueName.isEmpty){
addedNullValues
} else {
addedNullValues.mapField {
case JField(name, jValue) if idFieldToIdValueName.contains(name) => JField(name, JObject(JField(idFieldToIdValueName(name), jValue)))
case jField => jField
}
}
Extraction.extract(processedIdJObject,typeInfo).asInstanceOf[JsonFieldReName]
}
case x => throw new MappingException("Can't convert " + x + " to JsonFieldReName")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: JsonFieldReName => {
val ignoreFieldNames = getObjAnnotedFields(x, ru.typeOf[ignore])
val renamedJFields = ReflectUtils.getConstructorArgs(x)
.filter(pair => !ignoreFieldNames.contains(pair._1))
.map(pair => {
val paramName = StringHelpers.snakify(pair._1)
val paramValue = pair._2
isSomeId(paramValue) match {
case false => JField(paramName, Extraction.decompose(paramValue))
case true => {
val idValue = ReflectUtils.getConstructorArgs(paramValue).head._2
JField(paramName, Extraction.decompose(idValue))
}
}
}) .toList
JObject(renamedJFields)
}
}
private[this] def isNeedRenameFieldNames(entityType: Class[_], jValue: JValue): Boolean = {
val isJsonFieldRename = clazz.isAssignableFrom(entityType)
isJsonFieldRename &&
jValue.isInstanceOf[JObject] &&
jValue.asInstanceOf[JObject].obj.exists(jField => StringHelpers.camelifyMethod(jField.name) != jField.name)
}
// check given object is some Id, only type name ends with "Id" and have a single param constructor
private def isSomeId(obj: Any) = obj match {
case null => false
case _ => obj.getClass.getSimpleName.endsWith("Id") && ReflectUtils.getPrimaryConstructor(obj).asMethod.paramLists.headOption.exists(_.size == 1)
}
private def isSomeIdType(tp: ru.Type) = tp.typeSymbol.name.toString.endsWith("Id") && ReflectUtils.getConstructorParamInfo(tp).size == 1
/**
* extract constructor params those type is some id, and return the field name to the id constructor value name
* for example:
* case class Foo(name: String, bankId: BankId(value:String))
* getSomeIdFieldInfo(typeOf[Foo]) == Map(("bankId" -> "value"))
* @param clazz to do extract class
* @return field name to id type single value name
*/
private def getSomeIdFieldInfo(clazz: Class[_]) = {
val paramNameToType: Map[String, ru.Type] = ReflectUtils.getConstructorInfo(clazz)
paramNameToType
.filter(nameToType => isSomeIdType(nameToType._2))
.map(nameToType => {
val (name, paramType) = nameToType
val singleParamName = ReflectUtils.getConstructorParamInfo(paramType).head._1
(name, singleParamName)
}
)
}
private def getAnnotedFields(clazz: Class[_], annotationType: ru.Type): Map[String, ru.Type] = {
val symbol = ReflectUtils.classToSymbol(clazz)
ReflectUtils.getPrimaryConstructor(symbol.toType)
.paramLists.headOption.getOrElse(Nil)
.filter(param => param.annotations.exists(_.tree.tpe <:< annotationType))
.map(it => (it.name.toString, it.info))
.toMap
}
private def getObjAnnotedFields(obj: Any, annotationType: ru.Type): Map[String, ru.Type] = getAnnotedFields(obj.getClass, annotationType)
}
@scala.annotation.meta.field
@scala.annotation.meta.param
class ignore extends scala.annotation.StaticAnnotation
@scala.annotation.meta.field
@scala.annotation.meta.param
class optional extends scala.annotation.StaticAnnotation

View File

@ -23,6 +23,8 @@ object ReflectUtils {
def isObpType(tp: Type): Boolean = tp != null && tp.typeSymbol.isClass && OBP_TYPE_REGEX.findFirstIn(tp.typeSymbol.fullName).isDefined
def isObpClass(clazz: Class[_]): Boolean = clazz != null && OBP_TYPE_REGEX.findFirstIn(clazz.getName).isDefined
/**
* get given instance FieldMirror, and operate it. this function is just for helper of getField and setField function
* @param obj

View File

@ -86,6 +86,11 @@
<artifactId>lift-common_${scala.version}</artifactId>
<version>${lift.version}</version>
</dependency>
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-util_${scala.version}</artifactId>
<version>${lift.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>