From 5cee0dbffe93bbc9644631ba3e1901693b0c4768 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 17 Nov 2023 00:00:58 +0100 Subject: [PATCH 1/4] docfix/added the comments for javassist code --- .../main/scala/code/api/util/APIUtil.scala | 77 +++++++++++++------ .../scala/code/api/util/DynamicUtil.scala | 4 +- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 2128ee899c..6a36ecb6e8 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4176,41 +4176,56 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // (className, methodName, signature) -> List[(className, methodName, signature)] private val memo = new Memo[(String, String, String), List[(String, String, String)]] - private val cp = { + private val classPool = { val pool = ClassPool.getDefault // avoid error when call with JDK 1.8: // javassist.NotFoundException: code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1 pool.appendClassPath(new LoaderClassPath(Thread.currentThread.getContextClassLoader)) pool } + //NOTE: this will be also a issue, to set the big object classLoader as the cache key. private val memoClassPool = new Memo[ClassLoader, ClassPool] private def getClassPool(classLoader: ClassLoader) = memoClassPool.memoize(classLoader){ - val cp = ClassPool.getDefault - cp.appendClassPath(new LoaderClassPath(classLoader)) - cp + val classPool = ClassPool.getDefault + classPool.appendClassPath(new LoaderClassPath(classLoader)) + classPool } /** * NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. * according class name, method name and method's signature to get all dependent methods + * + * DependentMethods mean, the method used in the body, eg the following example, + * method0's dependentMethods are method1 and method2. + * please read `method.instrument(new ExprEditor()`, which will help us to get the dependent methods. + * + * def method1 = {} + * def method2 = {} + * + * def method0 = { + * method1 + * method2 + * } */ - def getDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = - if(SHOW_USED_CONNECTOR_METHODS){ - val methods = ListBuffer[(String, String, String)]() - //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. - val ctClass = cp.get(className) - val method = ctClass.getMethod(methodName, signature) - method.instrument(new ExprEditor() { - @throws[CannotCompileException] - override def edit(m: MethodCall): Unit = { - val tuple = (m.getClassName, m.getMethodName, m.getSignature) - methods += tuple - } - }) - methods.toList - } else { - Nil + def getOneLevelDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = { + if(SHOW_USED_CONNECTOR_METHODS){ + val methods = ListBuffer[(String, String, String)]() + //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. + val ctClass = classPool.get(className) + val method = ctClass.getMethod(methodName, signature) + //this instrument will get all the methods which are used in the method body. + method.instrument(new ExprEditor() { + @throws[CannotCompileException] + override def edit(m: MethodCall): Unit = {//it will loop all the used methods inside the method body. + val tuple = (m.getClassName, m.getMethodName, m.getSignature) + methods += tuple + } + }) + methods.toList + } else { + Nil + } } /** @@ -4218,8 +4233,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * get all dependent connector method names for an object * @param endpoint can be OBPEndpoint or other PartialFunction * @return a list of connector method name + * eg: one endpoint: + * */ - def getDependentConnectorMethods(endpoint: PartialFunction[_, _]): List[String] = + private def getDependentConnectorMethods(endpoint: PartialFunction[_, _]): List[String] = if (SHOW_USED_CONNECTOR_METHODS){ val connectorTypeName = classOf[Connector].getName val endpointClassName = endpoint.getClass.getName @@ -4229,26 +4246,36 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // } val classPool = this.getClassPool(endpoint.getClass.getClassLoader) - def getObpTrace(className: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = + /** + * this is a recursive funtion, it will get sub levels obp dependent methods. Please also read @getOneLevelDependentMethods + * inside the method body, we filter by `ReflectUtils.isObpClass` to find only OBP methods. + * + */ + def getAllLevelObpDependentMethods(className: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = memo.memoize((className, methodName, signature)) { // List:: className->methodName->signature - val methods = getDependentMethods(className, methodName, signature) + val methods = getOneLevelDependentMethods(className, methodName, signature) val list = methods.distinct.filter(it => ReflectUtils.isObpClass(it._1)).filterNot(exclude.contains) + list.collect { case x@(clazzName, _, _) if clazzName == connectorTypeName => x :: Nil case (clazzName, mName, mSignature) if !clazzName.startsWith("__wrapper$") => - getObpTrace(clazzName, mName, mSignature, list ::: exclude) + getAllLevelObpDependentMethods(clazzName, mName, mSignature, list ::: exclude) }.flatten.distinct } //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. + //in scala the partialFunction is also treat as a class, we can get it from classPool val ctClass = classPool.get(endpointClassName) // list of connector method name val connectorMethods: Array[String] = for { + // we loop all the declared methods for this endpoint. method <- ctClass.getDeclaredMethods - (clazzName, methodName, _) <- getObpTrace(endpointClassName, method.getName, method.getSignature) + //for each method, we will try to loop all the sub level methods it used. getAllLevelObpDepentMethods will return a list, + (clazzName, methodName, _) <- getAllLevelObpDependentMethods(endpointClassName, method.getName, method.getSignature) + //for the 2rd loop, we will check if it is the connector method, if clazzName == connectorTypeName && !methodName.contains("$default$") } yield methodName diff --git a/obp-api/src/main/scala/code/api/util/DynamicUtil.scala b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala index 11f8a2874a..55d6ce8047 100644 --- a/obp-api/src/main/scala/code/api/util/DynamicUtil.scala +++ b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala @@ -162,11 +162,11 @@ object DynamicUtil extends MdcLoggable{ for { method <- ctClass.getDeclaredMethods.toList if predicate(method.getName) - ternary @ (typeName, methodName, signature) <- APIUtil.getDependentMethods(className, method.getName, method.getSignature) + ternary @ (typeName, methodName, signature) <- APIUtil.getOneLevelDependentMethods(className, method.getName, method.getSignature) } yield { // if method is also dynamic compile code, extract it's dependent method if(className.startsWith(typeName) && methodName.startsWith(clazz.getPackage.getName+ "$")) { - listBuffer.appendAll(APIUtil.getDependentMethods(typeName, methodName, signature)) + listBuffer.appendAll(APIUtil.getOneLevelDependentMethods(typeName, methodName, signature)) } else { listBuffer.append(ternary) } From 239799e4f4d20e04bf9ed51396b53f5151ecc2d1 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 17 Nov 2023 01:06:40 +0100 Subject: [PATCH 2/4] docfix/added the comments for javassist code-step2 --- .../main/scala/code/api/util/APIUtil.scala | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 6a36ecb6e8..7918d881b0 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -81,7 +81,7 @@ import com.github.dwickern.macros.NameOf.{nameOf, nameOfType} import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import com.openbankproject.commons.model.enums.{ContentParam, PemCertificateRole, StrongCustomerAuthentication} -import com.openbankproject.commons.model.{Customer, UserAuthContext, _} +import com.openbankproject.commons.model._ import com.openbankproject.commons.util.Functions.Implicits._ import com.openbankproject.commons.util.Functions.Memo import com.openbankproject.commons.util._ @@ -4200,29 +4200,40 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * method0's dependentMethods are method1 and method2. * please read `method.instrument(new ExprEditor()`, which will help us to get the dependent methods. * - * def method1 = {} - * def method2 = {} + * def method0 = {} * - * def method0 = { - * method1 - * method2 + * def method1 = { + * method0 + * } + * def method2 = { + * method0 * } + * */ def getOneLevelDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = { - if(SHOW_USED_CONNECTOR_METHODS){ + if (SHOW_USED_CONNECTOR_METHODS) { val methods = ListBuffer[(String, String, String)]() //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. + //eg: className == code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1 + // ctClass == javassist.CtClassType@77e1b84c[public final class code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$.......... val ctClass = classPool.get(className) + //eg:methodName = isDefinedAt, sinature =(Lnet/liftweb/http/Req;)Z + // method => javassist.CtMethod@c40c7953[public final isDefinedAt (Lnet/liftweb/http/Req;)Z] val method = ctClass.getMethod(methodName, signature) - //this instrument will get all the methods which are used in the method body. + + //this instrument will add all the methods, whichever call this method. + // eg, the following 3 methods all call the `isDefinedAt`, then add all of them into the ListBuffer + //1 = {Tuple3@11566} (scala.Option,isEmpty,()Z) + //2 = {Tuple3@11567} (scala.Option,get,()Ljava/lang/Object;) + //3 = {Tuple3@11568} (scala.Tuple2,_1,()Ljava/lang/Object;) method.instrument(new ExprEditor() { @throws[CannotCompileException] - override def edit(m: MethodCall): Unit = {//it will loop all the used methods inside the method body. + override def edit(m: MethodCall): Unit = { //it will loop all the used methods inside the method body. val tuple = (m.getClassName, m.getMethodName, m.getSignature) methods += tuple } }) - methods.toList + methods.toList.distinct } else { Nil } @@ -4237,27 +4248,28 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * */ private def getDependentConnectorMethods(endpoint: PartialFunction[_, _]): List[String] = - if (SHOW_USED_CONNECTOR_METHODS){ + if (SHOW_USED_CONNECTOR_METHODS) { val connectorTypeName = classOf[Connector].getName val endpointClassName = endpoint.getClass.getName // not analyze dynamic code -// if(endpointClassName.startsWith("__wrapper$")) { -// return Nil -// } + // if(endpointClassName.startsWith("__wrapper$")) { + // return Nil + // } val classPool = this.getClassPool(endpoint.getClass.getClassLoader) /** * this is a recursive funtion, it will get sub levels obp dependent methods. Please also read @getOneLevelDependentMethods * inside the method body, we filter by `ReflectUtils.isObpClass` to find only OBP methods. - * + * The comment will take "className = code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1" + * as an example to explain the whole code. */ - def getAllLevelObpDependentMethods(className: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = - memo.memoize((className, methodName, signature)) { + def getAllLevelObpDependentMethods(endpointClassName: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = + memo.memoize((endpointClassName, methodName, signature)) { // List:: className->methodName->signature - val methods = getOneLevelDependentMethods(className, methodName, signature) + val methods = getOneLevelDependentMethods(endpointClassName, methodName, signature) val list = methods.distinct.filter(it => ReflectUtils.isObpClass(it._1)).filterNot(exclude.contains) - + list.collect { case x@(clazzName, _, _) if clazzName == connectorTypeName => x :: Nil case (clazzName, mName, mSignature) if !clazzName.startsWith("__wrapper$") => @@ -4267,17 +4279,42 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. //in scala the partialFunction is also treat as a class, we can get it from classPool - val ctClass = classPool.get(endpointClassName) - + val endpointCtClass = classPool.get(endpointClassName) + // list of connector method name val connectorMethods: Array[String] = for { // we loop all the declared methods for this endpoint. - method <- ctClass.getDeclaredMethods - //for each method, we will try to loop all the sub level methods it used. getAllLevelObpDepentMethods will return a list, - (clazzName, methodName, _) <- getAllLevelObpDependentMethods(endpointClassName, method.getName, method.getSignature) + // 0 = {CtMethod@11476} "javassist.CtMethod@13d04090[public final applyOrElse (Lnet/liftweb/http/Req;Lscala/Function1;)Ljava/lang/Object;]" + // 1 = {CtMethod@11477} "javassist.CtMethod@c40c7953[public final isDefinedAt (Lnet/liftweb/http/Req;)Z]" + // 2 = {CtMethod@11478} "javassist.CtMethod@481fb1f7[public final volatile isDefinedAt (Ljava/lang/Object;)Z]" + // 3 = {CtMethod@11479} "javassist.CtMethod@f1cb486c[public final volatile applyOrElse (Ljava/lang/Object;Lscala/Function1;)Ljava/lang/Object;]" + // 4 = {CtMethod@11480} "javassist.CtMethod@e38bb0a8[public static final $anonfun$applyOrElse$2 (Lscala/Tuple2;)Z]" + // 5 = {CtMethod@11481} "javassist.CtMethod@985bd81f[public static final $anonfun$applyOrElse$5 (Lcode/api/util/CallContext;)Lnet/liftweb/common/Box;]" + // 6 = {CtMethod@11482} "javassist.CtMethod@dbc18ec8[public static final $anonfun$applyOrElse$6 ()Lnet/liftweb/common/Empty$;]" + // 7 = {CtMethod@11483} "javassist.CtMethod@584acf1c[public static final $anonfun$applyOrElse$7 (Lcom/openbankproject/commons/model/User;)Lscala/Some;]" + // 8 = {CtMethod@11484} "javassist.CtMethod@dbc1964a[public static final $anonfun$applyOrElse$8 ()Lscala/None$;]" + // 9 = {CtMethod@11485} "javassist.CtMethod@efa42fa[public static final $anonfun$applyOrElse$9 (Lscala/Option;)Z]" + // 10 = {CtMethod@11486} "javassist.CtMethod@dcce4c5e[public static final $anonfun$applyOrElse$10 (Lscala/Option;)Lscala/Tuple2;]" + // 11 = {CtMethod@11487} "javassist.CtMethod@cec8127a[public static final $anonfun$applyOrElse$12 (Lnet/liftweb/json/JsonAST$JValue;)Lcode/api/UKOpenBanking/v3_1_0/JSONFactory_UKOpenBanking_310$ConsentPostBodyUKV310;]" + // 12 = {CtMethod@11488} "javassist.CtMethod@d072a654[public static final $anonfun$applyOrElse$16 (Lcode/model/Consumer;)Ljava/lang/String;]" + // 13 = {CtMethod@11489} "javassist.CtMethod@efd339d2[public static final $anonfun$applyOrElse$15 (Lcode/api/util/CallContext;)Lscala/Option;]" + // 14 = {CtMethod@11490} "javassist.CtMethod@4f13c6d1[public static final $anonfun$applyOrElse$14 (Lscala/Option;Lscala/Option;Lcode/api/UKOpenBanking/v3_1_0/JSONFactory_UKOpenBanking_310$ConsentPostBodyUKV310;)Lnet/liftweb/common/Box;]" + // 15 = {CtMethod@11491} "javassist.CtMethod@b9c14cb5[public static final $anonfun$applyOrElse$17 (Lscala/Option;Lnet/liftweb/common/Box;)Lcode/consent/ConsentTrait;]" + // 16 = {CtMethod@11492} "javassist.CtMethod@1f3dc9c[public static final $anonfun$applyOrElse$18 (Lcode/api/UKOpenBanking/v3_1_0/JSONFactory_UKOpenBanking_310$ConsentPostBodyUKV310;Lscala/Option;Lcode/consent/ConsentTrait;)Lscala/Tuple2;]" + // 17 = {CtMethod@11493} "javassist.CtMethod@936a98b2[public static final $anonfun$applyOrElse$13 (Lscala/Option;Lscala/Option;Lcode/api/UKOpenBanking/v3_1_0/JSONFactory_UKOpenBanking_310$ConsentPostBodyUKV310;)Lscala/concurrent/Future;]" + // 18 = {CtMethod@11494} "javassist.CtMethod@8f6fdab0[public static final $anonfun$applyOrElse$11 (Lscala/Option;Lnet/liftweb/json/JsonAST$JValue;Lscala/Tuple2;)Lscala/concurrent/Future;]" + // 19 = {CtMethod@11495} "javassist.CtMethod@46a5b15a[public static final $anonfun$applyOrElse$4 (Lscala/Option;Lnet/liftweb/json/JsonAST$JValue;Lscala/Tuple2;)Lscala/concurrent/Future;]" + // 20 = {CtMethod@11496} "javassist.CtMethod@506168ca[public static final $anonfun$applyOrElse$3 (Lnet/liftweb/json/JsonAST$JValue;Lscala/Tuple2;)Lscala/concurrent/Future;]" + // 21 = {CtMethod@11497} "javassist.CtMethod@ece0dbde[public static final $anonfun$applyOrElse$1 (Lnet/liftweb/json/JsonAST$JValue;Lcode/api/util/CallContext;)Lnet/liftweb/common/Box;]" + // 22 = {CtMethod@11498} "javassist.CtMethod@81d33197[public static final $anonfun$applyOrElse$9$adapted (Lscala/Option;)Ljava/lang/Object;]" + // 23 = {CtMethod@11499} "javassist.CtMethod@edb79645[public static final $anonfun$applyOrElse$2$adapted (Lscala/Tuple2;)Ljava/lang/Object;]" + // 24 = {CtMethod@11500} "javassist.CtMethod@d9d8e876[private static $deserializeLambda$ (Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;]" + endpointDeclaredMethod <- endpointCtClass.getDeclaredMethods + //for each method, we will try to loop all the sub level methods it used. getAllLevelObpDependentMethods will return a list, + (dependentClazzName, dependentMethodName, _) <- getAllLevelObpDependentMethods(endpointClassName, endpointDeclaredMethod.getName, endpointDeclaredMethod.getSignature) //for the 2rd loop, we will check if it is the connector method, - if clazzName == connectorTypeName && !methodName.contains("$default$") - } yield methodName + if dependentClazzName == connectorTypeName && !dependentMethodName.contains("$default$") + } yield dependentMethodName connectorMethods.toList.distinct } From d9ec4d88320ccf8e9e03cae538154fb25354f1ba Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 17 Nov 2023 09:24:57 +0100 Subject: [PATCH 3/4] docfix/added the comments for javassist code-step3 --- .../src/main/scala/code/api/util/APIUtil.scala | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 7918d881b0..90ca1cf8e6 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4221,19 +4221,29 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // method => javassist.CtMethod@c40c7953[public final isDefinedAt (Lnet/liftweb/http/Req;)Z] val method = ctClass.getMethod(methodName, signature) - //this instrument will add all the methods, whichever call this method. + //this exprEditor will add all the methods to ListBuffer, whichever method call this method. // eg, the following 3 methods all call the `isDefinedAt`, then add all of them into the ListBuffer //1 = {Tuple3@11566} (scala.Option,isEmpty,()Z) //2 = {Tuple3@11567} (scala.Option,get,()Ljava/lang/Object;) //3 = {Tuple3@11568} (scala.Tuple2,_1,()Ljava/lang/Object;) - method.instrument(new ExprEditor() { + // The ExprEditor allows you to define how the method's bytecode should be modified. + // You can use methods like insertBefore, insertAfter, replace, etc., to add, modify, + // or replace instructions within the method. + val exprEditor = new ExprEditor() { @throws[CannotCompileException] - override def edit(m: MethodCall): Unit = { //it will loop all the used methods inside the method body. + override def edit(m: MethodCall): Unit = { //it will be called whenever this method is used.. val tuple = (m.getClassName, m.getMethodName, m.getSignature) methods += tuple } - }) + } + + // The instrument method in Javassist is used to instrument or modify the bytecode of a method. + // This means you can dynamically insert, replace, or modify instructions in a method during runtime. + // just need to define your own expreEditor class + method.instrument(exprEditor) + methods.toList.distinct + } else { Nil } From 6b1736decdd424da07488d5c79821f72fb279e2b Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 17 Nov 2023 13:15:07 +0100 Subject: [PATCH 4/4] docfix/added the comments for javassist code-step4 --- .../main/scala/code/api/util/APIUtil.scala | 53 ++++++++++++------- .../scala/code/api/util/DynamicUtil.scala | 4 +- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 90ca1cf8e6..f952728491 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4200,17 +4200,28 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * method0's dependentMethods are method1 and method2. * please read `method.instrument(new ExprEditor()`, which will help us to get the dependent methods. * - * def method0 = {} - * - * def method1 = { - * method0 - * } - * def method2 = { - * method0 + * def method0 = { + * method1 + * method2 * } - * - */ - def getOneLevelDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = { + * + * def method1 = {} + * def method2 = {} + * eg: the partial function `lazy val createAccountAccessConsents : OBPEndpoint` first method `applicationAccess`, + * please check the applicationAccess method body, here is just first two lines of it: + * def applicationAccess(cc: CallContext): Future[(Box[User], Option[CallContext])] = + * getUserAndSessionContextFuture(cc) map { result => + * val url = result._2.map(_.url).getOrElse("None") + * ..... + * + * + * the className is `APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1` + * methodName is `applicationAccess()`, here is just example, in real compile code it may be mapped to different method + * signature is `Ljava/lang/Object;)` + * + * than the return value may be (getUserAndSessionContextFuture, ***,***),(map,***,***), (getOrElse,***,***) ...... + */ + def getDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = { if (SHOW_USED_CONNECTOR_METHODS) { val methods = ListBuffer[(String, String, String)]() //NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap. @@ -4221,7 +4232,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // method => javassist.CtMethod@c40c7953[public final isDefinedAt (Lnet/liftweb/http/Req;)Z] val method = ctClass.getMethod(methodName, signature) - //this exprEditor will add all the methods to ListBuffer, whichever method call this method. + //this exprEditor will read the method body line by, if it is a methodCall, we will add it into ListBuffer // eg, the following 3 methods all call the `isDefinedAt`, then add all of them into the ListBuffer //1 = {Tuple3@11566} (scala.Option,isEmpty,()Z) //2 = {Tuple3@11567} (scala.Option,get,()Ljava/lang/Object;) @@ -4273,17 +4284,18 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * The comment will take "className = code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1" * as an example to explain the whole code. */ - def getAllLevelObpDependentMethods(endpointClassName: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = - memo.memoize((endpointClassName, methodName, signature)) { - // List:: className->methodName->signature - val methods = getOneLevelDependentMethods(endpointClassName, methodName, signature) + def getObpTrace(clazzName: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = + memo.memoize((clazzName, methodName, signature)) { + // List:: className->methodName->signature, find all the dependent methods for one + val methods = getDependentMethods(clazzName, methodName, signature) + //this is just filter all the OBP classes. val list = methods.distinct.filter(it => ReflectUtils.isObpClass(it._1)).filterNot(exclude.contains) list.collect { case x@(clazzName, _, _) if clazzName == connectorTypeName => x :: Nil case (clazzName, mName, mSignature) if !clazzName.startsWith("__wrapper$") => - getAllLevelObpDependentMethods(clazzName, mName, mSignature, list ::: exclude) + getObpTrace(clazzName, mName, mSignature, list ::: exclude) }.flatten.distinct } @@ -4293,7 +4305,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // list of connector method name val connectorMethods: Array[String] = for { + // we loop all the declared methods for this endpoint. + // in scala PartialFunction is also a class, not a method. it will implicitly convert all method body code to class methods and fields. + // eg: the following methods are from "className = code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1" + // you can check the `lazy val createAccountAccessConsents : OBPEndpoint ` PartialFunction method body, the following are the converted methods: + // then for each method, need to analyse the code line by line, to find the methods it used, this is done by `getObpTrace` // 0 = {CtMethod@11476} "javassist.CtMethod@13d04090[public final applyOrElse (Lnet/liftweb/http/Req;Lscala/Function1;)Ljava/lang/Object;]" // 1 = {CtMethod@11477} "javassist.CtMethod@c40c7953[public final isDefinedAt (Lnet/liftweb/http/Req;)Z]" // 2 = {CtMethod@11478} "javassist.CtMethod@481fb1f7[public final volatile isDefinedAt (Ljava/lang/Object;)Z]" @@ -4320,8 +4337,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // 23 = {CtMethod@11499} "javassist.CtMethod@edb79645[public static final $anonfun$applyOrElse$2$adapted (Lscala/Tuple2;)Ljava/lang/Object;]" // 24 = {CtMethod@11500} "javassist.CtMethod@d9d8e876[private static $deserializeLambda$ (Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;]" endpointDeclaredMethod <- endpointCtClass.getDeclaredMethods - //for each method, we will try to loop all the sub level methods it used. getAllLevelObpDependentMethods will return a list, - (dependentClazzName, dependentMethodName, _) <- getAllLevelObpDependentMethods(endpointClassName, endpointDeclaredMethod.getName, endpointDeclaredMethod.getSignature) + //for each method, we will try to loop all the sub level methods it used. getObpTrace will return a list, + (dependentClazzName, dependentMethodName, _) <- getObpTrace(endpointClassName, endpointDeclaredMethod.getName, endpointDeclaredMethod.getSignature) //for the 2rd loop, we will check if it is the connector method, if dependentClazzName == connectorTypeName && !dependentMethodName.contains("$default$") } yield dependentMethodName diff --git a/obp-api/src/main/scala/code/api/util/DynamicUtil.scala b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala index 55d6ce8047..11f8a2874a 100644 --- a/obp-api/src/main/scala/code/api/util/DynamicUtil.scala +++ b/obp-api/src/main/scala/code/api/util/DynamicUtil.scala @@ -162,11 +162,11 @@ object DynamicUtil extends MdcLoggable{ for { method <- ctClass.getDeclaredMethods.toList if predicate(method.getName) - ternary @ (typeName, methodName, signature) <- APIUtil.getOneLevelDependentMethods(className, method.getName, method.getSignature) + ternary @ (typeName, methodName, signature) <- APIUtil.getDependentMethods(className, method.getName, method.getSignature) } yield { // if method is also dynamic compile code, extract it's dependent method if(className.startsWith(typeName) && methodName.startsWith(clazz.getPackage.getName+ "$")) { - listBuffer.appendAll(APIUtil.getOneLevelDependentMethods(typeName, methodName, signature)) + listBuffer.appendAll(APIUtil.getDependentMethods(typeName, methodName, signature)) } else { listBuffer.append(ternary) }