From b9b2dd89ab2259b705b9f56673ad423ced3acc31 Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 29 Jun 2020 17:03:03 +0800 Subject: [PATCH] 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. --- .../MessageDocsSwaggerDefinitions.scala | 2 +- .../code/api/util/CustomJsonFormats.scala | 156 +----------- .../akka/AkkaConnector_vDec2018.scala | 6 +- .../actor/SouthSideActorOfAkkaConnector.scala | 6 +- .../rest/RestConnector_vMar2019.scala | 16 +- .../storedprocedure/MSsqlStoredProcedure.sql | 62 ++--- .../StoredProcedureConnector_vDec2019.scala | 14 +- .../scala/code/management/ImporterAPI.scala | 4 +- obp-commons/pom.xml | 4 + .../commons/dto/JsonsTransfer.scala | 8 +- .../commons/model/CommonModel.scala | 36 +-- .../commons/model/PhysicalCardModel.scala | 59 +++-- .../commons/model/enums/Enumerations.scala | 25 +- .../commons/util/JsonAble.scala | 34 --- .../commons/util/JsonSerializers.scala | 230 ++++++++++++++++++ .../commons/util/ReflectUtils.scala | 2 + pom.xml | 5 + 17 files changed, 358 insertions(+), 311 deletions(-) delete mode 100644 obp-commons/src/main/scala/com/openbankproject/commons/util/JsonAble.scala create mode 100644 obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/MessageDocsSwaggerDefinitions.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/MessageDocsSwaggerDefinitions.scala index 5346aa12c..5c8b0ec02 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/MessageDocsSwaggerDefinitions.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/MessageDocsSwaggerDefinitions.scala @@ -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, 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 77f03b850..e0208332f 100644 --- a/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala +++ b/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala @@ -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 - - diff --git a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala index c39698660..ebf1e7fea 100644 --- a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala +++ b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala @@ -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)) diff --git a/obp-api/src/main/scala/code/bankconnectors/akka/actor/SouthSideActorOfAkkaConnector.scala b/obp-api/src/main/scala/code/bankconnectors/akka/actor/SouthSideActorOfAkkaConnector.scala index b623fc1a4..19517b521 100644 --- a/obp-api/src/main/scala/code/bankconnectors/akka/actor/SouthSideActorOfAkkaConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/akka/actor/SouthSideActorOfAkkaConnector.scala @@ -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( 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 eaef16c12..97f058dd0 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 @@ -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 } diff --git a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/MSsqlStoredProcedure.sql b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/MSsqlStoredProcedure.sql index 5cf38b6d5..fcf15b9e8 100644 --- a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/MSsqlStoredProcedure.sql +++ b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/MSsqlStoredProcedure.sql @@ -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":{ diff --git a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala index e607c2687..2826b6d35 100644 --- a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala @@ -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("[", ", ", "]") diff --git a/obp-api/src/main/scala/code/management/ImporterAPI.scala b/obp-api/src/main/scala/code/management/ImporterAPI.scala index d81831581..dae289e37 100644 --- a/obp-api/src/main/scala/code/management/ImporterAPI.scala +++ b/obp-api/src/main/scala/code/management/ImporterAPI.scala @@ -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._ /** diff --git a/obp-commons/pom.xml b/obp-commons/pom.xml index 08058ae48..2b31f9adc 100644 --- a/obp-commons/pom.xml +++ b/obp-commons/pom.xml @@ -26,6 +26,10 @@ net.liftweb lift-common_${scala.version} + + net.liftweb + lift-util_${scala.version} + org.scala-lang scala-reflect diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala b/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala index c37087e2b..7a00a4b0b 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala @@ -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, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index 79492087b..999c4ead0 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -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, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/PhysicalCardModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/PhysicalCardModel.scala index 364e8c0cb..f4f2922c6 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/PhysicalCardModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/PhysicalCardModel.scala @@ -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 + ) } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala index 8aafdbdc6..4745eb9d7 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala @@ -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 -} \ No newline at end of file +} + + +//-------------------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------------- \ No newline at end of file diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonAble.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonAble.scala deleted file mode 100644 index d77f0dfae..000000000 --- a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonAble.scala +++ /dev/null @@ -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()) - } -} \ No newline at end of file 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 new file mode 100644 index 000000000..6c46a9de2 --- /dev/null +++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala @@ -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 \ No newline at end of file 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 3ad1d76ce..0644de18d 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 @@ -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 diff --git a/pom.xml b/pom.xml index a9b84bbe6..59f054e99 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,11 @@ lift-common_${scala.version} ${lift.version} + + net.liftweb + lift-util_${scala.version} + ${lift.version} + org.apache.commons commons-collections4