diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 97d22200c..d0c685d6d 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -8,7 +8,7 @@ com.tesobe obp-parent ../pom.xml - 1.5.2 + 1.5.3 obp-api war diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index dad3d07e8..b1b0c4079 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -828,4 +828,8 @@ dynamic_endpoints_url_prefix=dynamic # Note: The user is also refreshed after every login. # You can also explicitly refresh the user using the refresh user endpoint. # refresh_user.interval=30 -# -------------------------------------------------------------------- \ No newline at end of file +# -------------------------------------------------------------------- + +## Inbound and Outbound ignore field names +inbound.ignore.fields= +outbound.ignore.fields=outboundAdapterCallContext.generalContext,outboundAdapterCallContext.outboundAdapterAuthInfo.linkedCustomers,outboundAdapterCallContext.outboundAdapterAuthInfo.authViews,outboundAdapterCallContext.outboundAdapterAuthInfo.authViews.view,outboundAdapterCallContext.outboundAdapterAuthInfo.authViews.account,outboundAdapterCallContext.outboundAdapterAuthInfo.authViews.view,outboundAdapterCallContext.outboundAdapterAuthInfo.authViews.account,inboundAdapterCallContext.generalContext 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 e0208332f..e4c6ae6b9 100644 --- a/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala +++ b/obp-api/src/main/scala/code/api/util/CustomJsonFormats.scala @@ -8,13 +8,20 @@ import code.api.cache.Caching import code.api.util.ApiRole.rolesMappedToClasses import code.api.v3_1_0.ListResult import code.util.Helper.MdcLoggable +import code.util.JsonUtils +import com.openbankproject.commons.util.Functions.Memo import com.openbankproject.commons.util._ import com.tesobe.CacheKeyFromArguments +import net.liftweb.json import net.liftweb.json.JsonAST.JValue import net.liftweb.json.{TypeInfo, _} +import org.apache.commons.lang3.StringUtils +import scala.collection.immutable.List import scala.concurrent.duration._ import scala.reflect.ManifestFactory +import scala.reflect.runtime.universe +import scala.reflect.runtime.universe._ trait CustomJsonFormats { implicit val formats: Formats = CustomJsonFormats.formats @@ -184,5 +191,58 @@ object JNothingSerializer extends Serializer[Any] with MdcLoggable { } } +object FieldIgnoreSerializer extends Serializer[AnyRef] { + private val memo = new Memo[universe.Type, List[String]]() + override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, json.JValue), AnyRef] = Functions.doNothing + private lazy val propsConfigIgnoreFields = Array( + APIUtil.getPropsValue("outbound.ignore.fields", "").split("""\s*,\s*"""), + APIUtil.getPropsValue("inbound.ignore.fields", "").split("""\s*,\s*""") + ).flatten.filterNot(StringUtils.isBlank).toList + + + override def serialize(implicit format: Formats): PartialFunction[Any, json.JValue] = { + case x if isInOutBoundType(x) => + val ignoreFieldNames: List[String] = getIgnores(ReflectUtils.getType(x)) ::: propsConfigIgnoreFields + val zson = json.Extraction.decompose(x)(CustomJsonFormats.formats) + ignoreFieldNames match { + case Nil => zson + case ignores => JsonUtils.deleteFields(zson, ignores) + } + } + + private def isInOutBoundType(any: Any) = { + if(ReflectUtils.isObpObject(any)) { + val className = any.getClass.getSimpleName + className.startsWith("OutBound") || className.startsWith("InBound") + } else { + false + } + } + + def getIgnores(tp: universe.Type): List[String] = { + if(!ReflectUtils.isObpType(tp)) { + return Nil + } + memo.memoize(tp, it => { + val fields: List[universe.Symbol] = it.decls.filter(decl => decl.isTerm && (decl.asTerm.isVal || decl.asTerm.isVar)).toList + val (ignoreFields, notIgnoreFields) = fields.partition(_.annotations.exists(_.tree.tpe <:< typeOf[ignore])) + val annotedFieldNames = ignoreFields.map(_.name.decodedName.toString.trim) + val subAnnotedFieldNames = notIgnoreFields.flatMap(it => { + val fieldName = it.name.decodedName.toString.trim + val fieldType: universe.Type = it.info match { + case x if x <:< typeOf[Iterable[_]] && !(x <:< typeOf[Map[_,_]]) => + x.typeArgs.head + case x if x <:< typeOf[Array[_]] => + x.typeArgs.head + case x => x + } + + getIgnores(fieldType) + .map(it => s"$fieldName.$it") + }) + annotedFieldNames ++ subAnnotedFieldNames + }) + } +} diff --git a/obp-api/src/main/scala/code/api/v2_2_0/JSONFactory2.2.0.scala b/obp-api/src/main/scala/code/api/v2_2_0/JSONFactory2.2.0.scala index c74a0e468..d2bcc9883 100644 --- a/obp-api/src/main/scala/code/api/v2_2_0/JSONFactory2.2.0.scala +++ b/obp-api/src/main/scala/code/api/v2_2_0/JSONFactory2.2.0.scala @@ -31,7 +31,7 @@ import java.util.Date import java.util.regex.Pattern import code.actorsystem.ObpActorConfig -import code.api.util.{APIUtil, ApiPropsWithAlias, CustomJsonFormats} +import code.api.util.{APIUtil, ApiPropsWithAlias, CustomJsonFormats, FieldIgnoreSerializer} import code.api.util.APIUtil.{MessageDoc, getPropsValue} import code.api.v1_2_1.BankRoutingJsonV121 import com.openbankproject.commons.model.{AccountRoutingJsonV121, AmountOfMoneyJsonV121} @@ -368,7 +368,7 @@ case class CustomerViewJsonV220( -object JSONFactory220 extends CustomJsonFormats { +object JSONFactory220 { def stringOrNull(text : String) = if(text == null || text.isEmpty) @@ -847,6 +847,8 @@ object JSONFactory220 extends CustomJsonFormats { MessageDocsJson(messageDocsList.map(createMessageDocJson)) } + private implicit val formats = CustomJsonFormats.formats + FieldIgnoreSerializer + def createMessageDocJson(md: MessageDoc): MessageDocJson = { val inBoundType = ReflectUtils.getType(md.exampleInboundMessage) diff --git a/obp-api/src/main/scala/code/util/JsonUtils.scala b/obp-api/src/main/scala/code/util/JsonUtils.scala index 8693c00f6..59c20eb5e 100644 --- a/obp-api/src/main/scala/code/util/JsonUtils.scala +++ b/obp-api/src/main/scala/code/util/JsonUtils.scala @@ -522,4 +522,36 @@ object JsonUtils { case _: JArray => typeOf[JArray] } } + + /** + * delete a group of field, field can be nested. + * @param jValue + * @param fields + * @return a new JValue that not contains given fields. + */ + def deleteFields(jValue: JValue, fields: List[String]) = fields match { + case Nil => jValue + case x => x.foldLeft(jValue)(deleteField) + } + + /** + * delete one field, the field can be nested, e.g: "foo.bar.barzz" + * @param jValue + * @param fieldName + * @return a new JValue that not contains given field. + */ + def deleteField(jValue:JValue, fieldName: String): JValue = jValue match { + case JNull | JNothing => jValue + case _: JObject => + if(!fieldName.contains(".")) { + jValue.removeField(_.name == fieldName) + } else { + val Array(field, nestedField) = StringUtils.split(fieldName, ".", 2) + jValue.transformField { + case JField(name, value) if name == field => JField(name, deleteField(value, nestedField)) + } + } + case JArray(arr) => JArray(arr.map(deleteField(_, fieldName))) + } + } diff --git a/obp-commons/pom.xml b/obp-commons/pom.xml index 4368a9875..4eff88ebe 100644 --- a/obp-commons/pom.xml +++ b/obp-commons/pom.xml @@ -7,7 +7,7 @@ com.tesobe obp-parent ../pom.xml - 1.5.2 + 1.5.3 obp-commons jar diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/Functions.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/Functions.scala index b2c966529..903f2f4ab 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/util/Functions.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/Functions.scala @@ -63,6 +63,24 @@ object Functions { coll.filterNot(it => it.isInstanceOf[Array[_]] || it.isInstanceOf[GenTraversableOnce[_]]) } + /** + * momoize function, to avoid re calculate values + * @tparam A key + * @tparam R cached value + */ + class Memo[A, R] { + private val cache = new java.util.concurrent.atomic.AtomicReference(Map[A, R]()) + + def memoize(x: A, f: A => R): R = { + val c = cache.get + def addToCache() = { + val ret = f(x) + cache.set(c + (x -> ret)) + ret + } + c.getOrElse(x, addToCache) + } + } // implicit functions place in this object object Implicits { diff --git a/pom.xml b/pom.xml index 6a948879f..638eccd89 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.tesobe obp-parent - 1.5.2 + 1.5.3 pom Open Bank Project API Parent 2011