diff --git a/obp-api/pom.xml b/obp-api/pom.xml
index 577ad5ee1..e4c134783 100644
--- a/obp-api/pom.xml
+++ b/obp-api/pom.xml
@@ -124,8 +124,6 @@
org.scalatest
scalatest_${scala.version}
- 3.0.5
- test
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 ddca29b46..ae9d2a910 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
@@ -9429,37 +9429,28 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
*/
private def convertId[T](obj: T, customerIdConverter: String=> String, accountIdConverter: String=> String): T = {
//1st: We must not convert when connector == mapped. this will ignore the implicitly_convert_ids props.
- //2rd: if connector != mapped, we still need the `implicitly_convert_ids == true`
- if(APIUtil.getPropsValue("connector","mapped") != "mapped" && APIUtil.getPropsAsBoolValue("implicitly_convert_ids",false)){
- ReflectUtils.operateNestedValues(obj)(fieldMirror => {
- val fieldValue = fieldMirror.get
- val fieldSymbol: TermSymbol = fieldMirror.symbol
- val fieldType: Type = fieldSymbol.info
- val fieldName: String = fieldSymbol.name.toString.trim.toLowerCase
-
- val ownerSymbol: Type = fieldSymbol.owner.asType.toType
-
- if(fieldValue == null) {
- // do nothing
- } else if (ownerSymbol <:< typeOf[CustomerId] ||
- (fieldName == "customerid" && fieldType =:= typeOf[String]) ||
- (ownerSymbol <:< typeOf[Customer] && fieldName == "id" && fieldType =:= typeOf[String])
- ) {
- val customerRef = customerIdConverter(fieldValue.asInstanceOf[String])
- fieldMirror.set(customerRef)
- } else if(ownerSymbol <:< typeOf[AccountId] ||
- (fieldName == "accountid" && fieldType =:= typeOf[String]) ||
- (ownerSymbol <:< typeOf[CoreAccount] && fieldName == "id" && fieldType =:= typeOf[String])||
- (ownerSymbol <:< typeOf[AccountBalance] && fieldName == "id" && fieldType =:= typeOf[String])||
- (ownerSymbol <:< typeOf[AccountHeld] && fieldName == "id" && fieldType =:= typeOf[String])
- ) {
- val accountRef = accountIdConverter(fieldValue.asInstanceOf[String])
- fieldMirror.set(accountRef)
- }
- })
- obj
- } else
- obj
+ //2rd: if connector != mapped, we still need the `implicitly_convert_ids == true`
+
+ def isCustomerId(fieldName: String, fieldType: Type, fieldValue: Any, ownerType: Type) = {
+ ownerType =:= typeOf[CustomerId] ||
+ (fieldName.equalsIgnoreCase("customerId") && fieldType =:= typeOf[String]) ||
+ (ownerType <:< typeOf[Customer] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])
+ }
+
+ def isAccountId(fieldName: String, fieldType: Type, fieldValue: Any, ownerType: Type) = {
+ ownerType <:< typeOf[AccountId] ||
+ (fieldName.equalsIgnoreCase("accountId") && fieldType =:= typeOf[String])
+ (ownerType <:< typeOf[CoreAccount] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
+ (ownerType <:< typeOf[AccountBalance] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])||
+ (ownerType <:< typeOf[AccountHeld] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])
+ }
+
+ ReflectUtils.resetNestedFields(obj){
+ case (fieldName, fieldType, fieldValue: String, ownerType) if isCustomerId(fieldName, fieldType, fieldValue, ownerType) => customerIdConverter(fieldValue)
+ case (fieldName, fieldType, fieldValue: String, ownerType) if isAccountId(fieldName, fieldType, fieldValue, ownerType) => accountIdConverter(fieldValue)
+ }
+
+ obj
}
/**
diff --git a/obp-commons/pom.xml b/obp-commons/pom.xml
index 2b280f799..91e4084ed 100644
--- a/obp-commons/pom.xml
+++ b/obp-commons/pom.xml
@@ -13,6 +13,14 @@
jar
Open Bank Project Commons
+
+
+ artima
+ Artima Maven Repository
+ http://repo.artima.com/releases
+
+
+
net.liftweb
@@ -27,13 +35,23 @@
org.scalatest
scalatest_${scala.version}
- 3.0.5
- test
+
+
+ org.scalactic
+ scalactic_${scala.version}
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${scala.version}
+
+ true
+
+
net.alchim31.maven
scala-maven-plugin
@@ -47,6 +65,13 @@
${scala.version}
incremental
true
+
+
+ com.artima.supersafe
+ supersafe_${scala.compiler}
+ 1.1.8
+
+
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/OBPEnum.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/OBPEnum.scala
index 8fb01d5a2..4c04a117a 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/util/OBPEnum.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/OBPEnum.scala
@@ -71,8 +71,6 @@ object OBPEnumeration {
def getValuesByClass[T <: EnumValue](clazz: Class[T]): List[T] = getEnumContainer(clazz).values
- def getValues[T <: EnumValue: TypeTag]: List[T] = getEnumContainer(typeTag[T].tpe).values.map(_.asInstanceOf[T])
-
def withNameOption(tp: Type, name: String): Option[EnumValue] = getEnumContainer(tp).withNameOption(name).map(_.asInstanceOf[EnumValue])
def withNameOption[T <: EnumValue](clazz: Class[T], name: String): Option[T] = getEnumContainer(clazz).withNameOption(name)
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 c38c2b6b2..593ac8bb2 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
@@ -56,14 +56,17 @@ object ReflectUtils {
def setField[T](obj: AnyRef, fieldName: String, fieldValue: T): T = operateField[T](obj, fieldName, _.set(fieldValue))
/**
- * modify given instance nested values
+ * modify given instance nested fields value
* @param obj given instance to modify
* @param predicate check whether current field value need to modify
- * @param fn modify function
+ * @param fn modify function, signature is (fieldName: String, fieldType: Type, fieldValue: Any, ownerType: Type): Any
+ * fn result is calculated new value
* @return modified instance
+ *
+ * @note be carefully, this method will modify immutable object state, before you call this method, you should very sure it is safe for your logic.
*/
- def operateNestedValues(obj: Any, predicate: Any => Boolean = isObpObject)(fn: ru.FieldMirror => Unit): Any = {
- val recurseCallback = operateNestedValues(_: Any, predicate)(fn)
+ def resetNestedFields(obj: Any, predicate: Any => Boolean = isObpObject)(fn: PartialFunction[(String, Type, Any, Type), Any]): Any = {
+ val recurseCallback = resetNestedFields(_: Any, predicate)(fn)
obj match {
case null | None | Empty | Nil => obj
case _: Unit => obj
@@ -85,7 +88,7 @@ object ReflectUtils {
case v: Array[_] => v.map(recurseCallback)
case v: Map[_, _] => v.values.map(recurseCallback)
case v: Iterable[_] => v.map(recurseCallback)
- case v if(!predicate(v)) => v
+ case v if !predicate(v) => v
case _ => {
val tp = this.getType(obj)
val instanceMirror: ru.InstanceMirror = mirror.reflect(obj)
@@ -98,8 +101,18 @@ object ReflectUtils {
constructFieldNames.foreach(it => {
val fieldSymbol: ru.TermSymbol = getType(obj).member(ru.TermName(it)).asTerm.accessed.asTerm
val fieldMirror: ru.FieldMirror = instanceMirror.reflectField(fieldSymbol)
- recurseCallback(fieldMirror.get)
- fn(fieldMirror)
+ val fieldValue: Any = fieldMirror.get
+ recurseCallback(fieldValue)
+
+ //check whether field should modify, if PartialFunction check result is true, just modify it with new Value
+ val fieldName: String = it
+ val fieldType: Type = fieldSymbol.info
+ val ownerType: Type = fieldSymbol.owner.asType.toType
+
+ if(fn.isDefinedAt(fieldName, fieldType, fieldValue, ownerType)) {
+ val newValue = fn(fieldName, fieldType, fieldValue, ownerType)
+ fieldMirror.set(newValue)
+ }
})
obj
}
diff --git a/obp-commons/src/test/scala/com/openbankproject/commons/util/OBPEnumerationTest.scala b/obp-commons/src/test/scala/com/openbankproject/commons/util/OBPEnumerationTest.scala
new file mode 100644
index 000000000..e19c768ba
--- /dev/null
+++ b/obp-commons/src/test/scala/com/openbankproject/commons/util/OBPEnumerationTest.scala
@@ -0,0 +1,241 @@
+package com.openbankproject.commons.util
+
+import com.openbankproject.commons.util.Color.Color
+import com.openbankproject.commons.util.Shape.Shape
+import org.scalatest._
+
+import scala.reflect.runtime.universe._
+
+import org.scalatest.Tag
+
+// to show bad design of scala enumeration
+object Shape extends Enumeration {
+ type Shape = Value
+ val Circle = Value
+ val Square = Value
+ val Other = Value
+}
+
+object Color extends Enumeration {
+ type Color = Value
+ val Red = Value
+ val Green = Value
+ val Other = Value
+}
+
+object OBPEnumTag extends Tag("OBPEnumeration")
+/**
+ * just for demonstrate what problem of scala enumeration, so here just set to ignore
+ */
+@Ignore
+class ScalaEnumerationTest extends FlatSpec with Matchers {
+
+ it should "legal to create two overloaded methods with parameter Shape and Color" taggedAs(OBPEnumTag) in {
+ // if remove the comment of process method, will can't compile
+ object OverloadTest{
+ def process(shape: Shape) = ???
+// def process(Color: Color) = ???
+ }
+ }
+
+ it should "have compile warnings when match case is not exhaustive with enumeration" taggedAs(OBPEnumTag) in {
+ val shape: Shape = Shape.Other
+ shape match {
+ case Shape.Other => println("hi other")
+ }
+ }
+
+ "shape.isInstanceOf[Color] " should "return false" taggedAs(OBPEnumTag) in {
+ // the worst: confused type check
+ val shape: Shape = Shape.Other
+
+ // shape is not Color, isColor should be false
+ val isColor = shape.isInstanceOf[Color]
+ isColor shouldBe false
+ }
+ "shape cast to Color" should "throw ClassCastException" taggedAs(OBPEnumTag) in {
+ val shape: Shape = Shape.Other
+ // shape cast to Color should throw ClassCastException
+ a[ClassCastException] should be thrownBy {
+ val color: Color = shape.asInstanceOf[Color]
+ }
+ }
+ "if shape can be cast to Color, casted value" should "match Color value" taggedAs(OBPEnumTag) in {
+ val shape: Shape = Shape.Other
+ val color: Color = shape.asInstanceOf[Color]
+ val wrong: String = color match {
+ case Color.Other => "match Color#other"
+ case _ => "NOT MATCH"
+ }
+ color.toString shouldBe "Other"
+
+ wrong shouldBe "match Color#other"
+ }
+}
+
+
+// to demonstrate OBPEnumeration
+sealed trait OBPShape extends EnumValue
+
+object OBPShape extends OBPEnumeration[OBPShape]{
+ object Circle extends OBPShape
+ object Square extends OBPShape
+ object Other extends OBPShape
+}
+
+sealed trait OBPColor extends EnumValue
+
+object OBPColor extends OBPEnumeration[OBPColor]{
+ object Red extends OBPColor
+ object Green extends OBPColor
+ object Other extends OBPColor
+}
+
+class OBPEnumerationTest extends FlatSpec with Matchers {
+ it should "legal to create two overloaded methods with parameter OBPShape and OBPColor" taggedAs(OBPEnumTag) in {
+ // first bad: can't overload for different enumeration
+ object OverloadTest{
+ def process(shape: OBPShape) = ???
+ def process(Color: OBPColor) = ???
+ }
+ }
+
+ it should "have compile warnings when match case is not exhaustive with enumeration" taggedAs(OBPEnumTag) in {
+ val shape: OBPShape = OBPShape.Other
+ shape match {
+ case OBPShape.Other => "hi other"
+ }
+ }
+
+ "shape.isInstanceOf[OBPColor] " should "return false" taggedAs(OBPEnumTag) in {
+ // the worst: confused type check
+ val shape: OBPShape = OBPShape.Other
+
+ // shape is not Color, isColor should be false
+ val isColor = shape.isInstanceOf[OBPColor]
+ isColor shouldBe false
+ }
+
+ "shape cast to Color" should "throw ClassCastException" taggedAs(OBPEnumTag) in {
+ val shape: OBPShape = OBPShape.Other
+ // shape cast to Color should throw ClassCastException
+ a [ClassCastException] should be thrownBy {
+ val color = shape.asInstanceOf[OBPColor]
+ }
+ }
+
+ "values" should "contains all values" taggedAs(OBPEnumTag) in {
+ OBPShape.values should contain only (OBPShape.Square, OBPShape.Circle, OBPShape.Other)
+ }
+
+ "example" should "be one value of OBPShape enumeration" taggedAs(OBPEnumTag) in {
+ OBPShape.example shouldBe a [OBPShape]
+ }
+ "nameToValue" should "be name map to value of OBPShape enumeration" taggedAs(OBPEnumTag) in {
+ OBPShape.nameToValue should contain theSameElementsAs Map("Square" -> OBPShape.Square, "Circle" -> OBPShape.Circle, "Other" -> OBPShape.Other)
+ }
+
+ it should "get a Some(v) value when call withNameOption to retrieve exists OBPShape value" taggedAs(OBPEnumTag) in {
+ OBPShape.withNameOption("Square") shouldBe Some(OBPShape.Square)
+ }
+
+ it should "get a None value when call withNameOption to retrieve not exists OBPShape value" taggedAs(OBPEnumTag) in {
+ OBPShape.withNameOption("NOT_EXISTS") shouldBe None
+ }
+
+ it should "get a value when call withName to retrieve exists OBPShape value" taggedAs(OBPEnumTag) in {
+ OBPShape.withName("Circle") shouldBe OBPShape.Circle
+ }
+
+ it should "throw NoSuchElementException when call withName to retrieve not exists OBPShape value" taggedAs(OBPEnumTag) in {
+ a [NoSuchElementException] should be thrownBy {
+ OBPShape.withName("NOT_EXISTS") shouldBe OBPShape.Circle
+ }
+ }
+
+ // the follow test is for OBPEnumeration utils functions
+ "call OBPEnumeration.getValuesByType with an OBPEnumeration type" should "get all values" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPColor]
+ OBPEnumeration.getValuesByType(unknownType) shouldBe(OBPColor.values)
+ }
+ "call OBPEnumeration.getValuesByClass with an OBPEnumeration type" should "get all values" taggedAs(OBPEnumTag) in {
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+ OBPEnumeration.getValuesByClass(unknownClazz) should be(OBPShape.values)
+ }
+ "call OBPEnumeration.withNameOption with an OBPEnumeration type OR class and exists name" should "get correct Some(v)" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withNameOption(unknownType, "Square") shouldBe Some(OBPShape.Square)
+ OBPEnumeration.withNameOption(unknownClazz, "Circle") shouldBe Some(OBPShape.Circle)
+ }
+ "call OBPEnumeration.withNameOption with an OBPEnumeration type OR class and not exists name" should "get correct None" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withNameOption(unknownType, "NOT_EXISTS") shouldBe empty
+ OBPEnumeration.withNameOption(unknownClazz, "NOT_EXISTS") shouldBe empty
+ }
+ "call OBPEnumeration.withName with an OBPEnumeration type OR class and exists name" should "get correct value" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withName(unknownType, "Square") shouldBe OBPShape.Square
+ OBPEnumeration.withName(unknownClazz, "Circle") shouldBe OBPShape.Circle
+ }
+ "call OBPEnumeration.withName with an OBPEnumeration type OR class and not exists name" should "throw NoSuchElementException" taggedAs(OBPEnumTag) in {
+ a [NoSuchElementException] should be thrownBy {
+ val unknownType = typeOf[OBPShape]
+ OBPEnumeration.withName(unknownType, "NOT_EXISTS")
+ }
+
+ a [NoSuchElementException] should be thrownBy {
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+ OBPEnumeration.withName(unknownClazz, "NOT_EXISTS")
+ }
+ }
+
+
+ "call OBPEnumeration.withIndexOption with an OBPEnumeration type OR class and exists taggedAs(OBPEnumTag) index" should "get correct Some(v)" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withIndexOption(unknownType, 0) shouldBe Some(OBPShape.Circle)
+ OBPEnumeration.withIndexOption(unknownClazz, 1) shouldBe Some(OBPShape.Square)
+ }
+ "call OBPEnumeration.withIndexOption with an OBPEnumeration type OR class and not exists taggedAs(OBPEnumTag) index" should "get correct None" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withIndexOption(unknownType, 4) shouldBe empty
+ OBPEnumeration.withIndexOption(unknownClazz, -1) shouldBe empty
+ }
+ "call OBPEnumeration.withIndex with an OBPEnumeration type OR class and exists taggedAs(OBPEnumTag) index" should "get correct value" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+
+ OBPEnumeration.withIndex(unknownType, 0) shouldBe OBPShape.Circle
+ OBPEnumeration.withIndex(unknownClazz, 1) shouldBe OBPShape.Square
+ }
+ "call OBPEnumeration.withIndex with an OBPEnumeration type OR class and not exists taggedAs(OBPEnumTag) index" should "throw NoSuchElementException" taggedAs(OBPEnumTag) in {
+ a [NoSuchElementException] should be thrownBy {
+ val unknownType = typeOf[OBPShape]
+ OBPEnumeration.withIndex(unknownType, 8)
+ }
+
+ a [NoSuchElementException] should be thrownBy {
+ val unknownClazz = classOf[OBPShape].asInstanceOf[Class[EnumValue]]
+ OBPEnumeration.withIndex(unknownClazz, -1)
+ }
+ }
+
+ "call OBPEnumeration.getExampleByType" should "get one of values" taggedAs(OBPEnumTag) in {
+ val unknownType = typeOf[OBPShape]
+ OBPEnumeration.getExampleByType(unknownType) shouldBe(OBPShape.Circle)
+ }
+
+ "call OBPEnumeration.getExampleByClass" should "get one of values" taggedAs(OBPEnumTag) in {
+ val unknownClazz = classOf[OBPColor].asInstanceOf[Class[EnumValue]]
+ OBPEnumeration.getExampleByClass(unknownClazz) shouldBe(OBPColor.Red)
+ }
+}
\ No newline at end of file
diff --git a/obp-commons/src/test/scala/com/openbankproject/commons/util/ReflectUtilsTest.scala b/obp-commons/src/test/scala/com/openbankproject/commons/util/ReflectUtilsTest.scala
new file mode 100644
index 000000000..44b7a64e3
--- /dev/null
+++ b/obp-commons/src/test/scala/com/openbankproject/commons/util/ReflectUtilsTest.scala
@@ -0,0 +1,32 @@
+package com.openbankproject.commons.util
+
+import org.scalatest.{FlatSpec, Matchers}
+import scala.reflect.runtime.universe._
+import org.scalatest.Tag
+
+class ReflectUtilsTest extends FlatSpec with Matchers {
+ object ReflectUtilsTag extends Tag("ReflectUtils")
+
+ case class Aperson(id: String, age: Int)
+ case class Agroup(manager: Aperson, id: Int, members: List[Aperson])
+
+
+ "when modify Apersion#id to append suffix" should "all the not null id be end with suffix" taggedAs(ReflectUtilsTag) in {
+ val members = List(Aperson(null, 10), Aperson("p1-id", 20), Aperson("p2-id", 3))
+ val group = Agroup(Aperson("m-id", 11), 3, members)
+ val someGroup = Some(group)
+
+ val idSuffix = "---END"
+
+ ReflectUtils.resetNestedFields(someGroup){
+ case (fieldName, fieldType, fieldValue: String, ownerType) if(fieldName == "id" && ownerType =:= typeOf[Aperson]) =>
+ fieldValue + idSuffix
+ }
+
+ group.manager.id should endWith (idSuffix)
+ group.id shouldBe(3)
+ group.members.head.id shouldBe null
+ group.members.lift(1).get.id should endWith (idSuffix)
+ group.members.lift(2).get.id should endWith (idSuffix)
+ }
+}
diff --git a/pom.xml b/pom.xml
index b45925766..5470e55ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,18 @@
lift-common_${scala.version}
${lift.version}
+
+ org.scalatest
+ scalatest_${scala.version}
+ 3.0.8
+ test
+
+
+ org.scalactic
+ scalactic_${scala.version}
+ 3.0.8
+ test
+