mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 13:26:51 +00:00
Merge branch 'feature/dynamic_code_sandbox' into develop-Simon
# Conflicts: # obp-api/src/main/resources/props/sample.props.template # obp-api/src/main/scala/code/api/util/APIUtil.scala # obp-api/src/main/scala/code/api/util/ErrorMessages.scala # obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
This commit is contained in:
commit
3407a22a62
@ -8,7 +8,7 @@
|
||||
<groupId>com.tesobe</groupId>
|
||||
<artifactId>obp-parent</artifactId>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<version>1.10.0</version>
|
||||
<version>1.10.1</version>
|
||||
</parent>
|
||||
<artifactId>obp-api</artifactId>
|
||||
<packaging>war</packaging>
|
||||
@ -479,7 +479,7 @@
|
||||
|
||||
<!--macro cache-->
|
||||
<dependency>
|
||||
<groupId>com.github.oldbig</groupId>
|
||||
<groupId>com.github.OpenBankProject</groupId>
|
||||
<artifactId>macmemo</artifactId>
|
||||
<version>0.6-OBP-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
@ -1113,4 +1113,11 @@ personal_data_collection_consent_country_waiver_list = Austria, Belgium, Bulgari
|
||||
|
||||
# Local identity provider url
|
||||
# it defaults to the hostname props value
|
||||
# local_identity_provider=this is the hostname of the local obp server including scheme
|
||||
# local_identity_provider=this is the hostname of the local obp server including scheme
|
||||
|
||||
|
||||
# enable dynamic code sandbox, default is false, this will make sandbox works for code running in Future, will make performance lower than disable
|
||||
dynamic_code_sandbox_enable=false
|
||||
# enable dynamic code compile validation, default is false, if set it to true, it will validate all the dynamic method body when you create/update any
|
||||
# dynamic scala method. Note, it only check all the obp code dependents for all the method in scala code.
|
||||
dynamic_code_compile_validate_enable=false
|
||||
@ -217,9 +217,15 @@ class Boot extends MdcLoggable {
|
||||
logger.info("external props folder: " + propsPath)
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
logger.info("Current Project TimeZone: " + TimeZone.getDefault)
|
||||
|
||||
|
||||
// set dynamic_code_sandbox_enable to System.properties, so com.openbankproject.commons.ExecutionContext can read this value
|
||||
APIUtil.getPropsValue("dynamic_code_sandbox_enable")
|
||||
.foreach(it => System.setProperty("dynamic_code_sandbox_enable", it))
|
||||
}
|
||||
|
||||
|
||||
|
||||
def boot {
|
||||
// set up the way to connect to the relational DB we're using (ok if other connector than relational)
|
||||
if (!DB.jndiJdbcConnAvailable_?) {
|
||||
|
||||
@ -113,15 +113,18 @@ object ApiVersionHolder {
|
||||
* This is helpful if you want send back given error message and status code
|
||||
* @param jsonResponse
|
||||
*/
|
||||
case class JsonResponseException(jsonResponse: JsonResponse) extends RuntimeException with NoStackTrace {
|
||||
case class JsonResponseException(jsonResponse: JsonResponse) extends RuntimeException with NoStackTrace
|
||||
|
||||
object JsonResponseException {
|
||||
/**
|
||||
*
|
||||
* @param errorMsg error message
|
||||
* @param errorCode response error code and status code
|
||||
* @param correlationId this value can be got from callContext
|
||||
*/
|
||||
def this(errorMsg: String, errorCode: Int, correlationId: String) =
|
||||
this(createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String))
|
||||
def apply(errorMsg: String, errorCode: Int, correlationId: String):JsonResponseException = {
|
||||
JsonResponseException(createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String))
|
||||
}
|
||||
}
|
||||
|
||||
trait OBPRestHelper extends RestHelper with MdcLoggable {
|
||||
|
||||
@ -4281,6 +4281,7 @@ object SwaggerDefinitionsJSON {
|
||||
val jsonConnectorMethodMethodBody = JsonConnectorMethodMethodBody(connectorMethodBodyExample.value)
|
||||
|
||||
val jsonDynamicResourceDoc = JsonDynamicResourceDoc(
|
||||
bankId = Some(bankIdExample.value),
|
||||
dynamicResourceDocId = Some(dynamicResourceDocIdExample.value),
|
||||
methodBody = dynamicResourceDocMethodBodyExample.value,
|
||||
partialFunctionName = dynamicResourceDocPartialFunctionNameExample.value,
|
||||
@ -4296,6 +4297,7 @@ object SwaggerDefinitionsJSON {
|
||||
)
|
||||
|
||||
val jsonDynamicMessageDoc = JsonDynamicMessageDoc(
|
||||
bankId = Some(bankIdExample.value),
|
||||
dynamicMessageDocId = Some(dynamicMessageDocIdExample.value),
|
||||
process = processExample.value,
|
||||
messageFormat = messageFormatExample.value,
|
||||
@ -4494,6 +4496,8 @@ object SwaggerDefinitionsJSON {
|
||||
description = descriptionExample.value,
|
||||
meta = metaJson,
|
||||
)
|
||||
|
||||
val requestRootJsonClass = dynamic.practise.PractiseEndpoint.RequestRootJsonClass(name = nameExample.value, age=ageExample.value.toLong, Nil)
|
||||
|
||||
val entitlementJsonV400 = EntitlementJsonV400(
|
||||
entitlement_id = entitlementIdExample.value,
|
||||
|
||||
@ -33,7 +33,6 @@ import java.nio.charset.Charset
|
||||
import java.text.{ParsePosition, SimpleDateFormat}
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.{Calendar, Date, UUID}
|
||||
|
||||
import code.UserRefreshes.UserRefreshes
|
||||
import code.accountholders.AccountHolders
|
||||
import code.api.Constant._
|
||||
@ -45,6 +44,7 @@ import code.api.util.APIUtil.ResourceDoc.{findPathVariableNames, isPathVariable}
|
||||
import code.api.util.ApiRole.{canCreateProduct, canCreateProductAtAnyBank}
|
||||
import code.api.util.ApiTag.{ResourceDocTag, apiTagBank, apiTagNewStyle}
|
||||
import code.api.util.Glossary.GlossaryItem
|
||||
import code.api.util.RateLimitingJson.CallLimit
|
||||
import code.api.v1_2.ErrorMessage
|
||||
import code.api.v2_0_0.CreateEntitlementJSON
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
|
||||
@ -95,6 +95,17 @@ import scala.collection.JavaConverters._
|
||||
import scala.collection.immutable.{List, Nil}
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import com.openbankproject.commons.util.{ApiVersion, Functions, JsonAble, ReflectUtils, ScannedApiVersion}
|
||||
import com.openbankproject.commons.util.Functions.Implicits._
|
||||
import com.openbankproject.commons.util.Functions.Memo
|
||||
import javassist.{ClassPool, LoaderClassPath}
|
||||
import javassist.expr.{ExprEditor, MethodCall}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.security.AccessControlException
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.Future
|
||||
import scala.io.BufferedSource
|
||||
import scala.util.Either
|
||||
@ -2478,7 +2489,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case _ => laf.abort
|
||||
}
|
||||
scf.onFailure {
|
||||
case e: Throwable => laf.fail(Failure(e.getMessage(), Full(e), Empty))
|
||||
case e: AccessControlException =>
|
||||
laf.fail(Failure(s"$DynamicResourceDocMethodPermission No permission of: ${e.getPermission.toString}", Full(e), Empty))
|
||||
|
||||
case e: Throwable =>
|
||||
laf.fail(Failure(e.getMessage(), Full(e), Empty))
|
||||
}
|
||||
laf
|
||||
}
|
||||
@ -3751,40 +3766,61 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
pool.appendClassPath(new LoaderClassPath(Thread.currentThread.getContextClassLoader))
|
||||
pool
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* according class name, method name and method's signature to get all dependent methods
|
||||
*/
|
||||
def getDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = {
|
||||
val methods = ListBuffer[(String, String, String)]()
|
||||
val method = cp.get(className).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
|
||||
}
|
||||
|
||||
/**
|
||||
* get all dependent connector method names for an object
|
||||
* @param endpoint can be OBPEndpoint or other PartialFunction
|
||||
* @return a list of connector method name
|
||||
*/
|
||||
def getDependentConnectorMethods(endpoint: PartialFunction[_, _]) = {
|
||||
def getDependentConnectorMethods(endpoint: PartialFunction[_, _]): List[String] = {
|
||||
val connectorTypeName = classOf[Connector].getName
|
||||
val endpointClassName = endpoint.getClass.getName
|
||||
// not analyze dynamic code
|
||||
// if(endpointClassName.startsWith("__wrapper$")) {
|
||||
// return Nil
|
||||
// }
|
||||
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)] =
|
||||
memo.memoize((className, methodName, signature)) {
|
||||
val methods = ListBuffer[(String, String, String)]()
|
||||
val method = cp.get(className).getMethod(methodName, signature)
|
||||
method.instrument(new ExprEditor() {
|
||||
@throws[CannotCompileException]
|
||||
override def edit(m: MethodCall): Unit = {
|
||||
val tuple = (m.getClassName, m.getMethodName, m.getSignature)
|
||||
if (ReflectUtils.isObpClass(m.getClassName)) {
|
||||
methods += tuple
|
||||
}
|
||||
}
|
||||
})
|
||||
// List:: className->methodName->signature
|
||||
val methods = getDependentMethods(className, methodName, signature)
|
||||
|
||||
val list = methods.distinct.toList.filterNot(exclude.contains)
|
||||
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) =>
|
||||
case (clazzName, mName, mSignature) if !clazzName.startsWith("__wrapper$") =>
|
||||
getObpTrace(clazzName, mName, mSignature, list ::: exclude)
|
||||
}.flatten.distinct
|
||||
}
|
||||
|
||||
val endpointClassName = endpoint.getClass.getName
|
||||
|
||||
// list of connector method name
|
||||
val connectorMethods: Array[String] = for {
|
||||
method <- cp.get(endpoint.getClass.getName).getDeclaredMethods
|
||||
method <- classPool.get(endpointClassName).getDeclaredMethods
|
||||
(clazzName, methodName, _) <- getObpTrace(endpointClassName, method.getName, method.getSignature)
|
||||
if clazzName == connectorTypeName && !methodName.contains("$default$")
|
||||
} yield methodName
|
||||
|
||||
@ -752,9 +752,27 @@ object ApiRole {
|
||||
|
||||
case class CanDeleteDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canDeleteDynamicResourceDoc = CanDeleteDynamicResourceDoc()
|
||||
|
||||
case class CanCreateBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canCreateBankLevelDynamicResourceDoc = CanCreateBankLevelDynamicResourceDoc()
|
||||
|
||||
case class CanUpdateBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canUpdateBankLevelDynamicResourceDoc = CanUpdateBankLevelDynamicResourceDoc()
|
||||
|
||||
case class CanGetBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetBankLevelDynamicResourceDoc = CanGetBankLevelDynamicResourceDoc()
|
||||
|
||||
case class CanGetAllBankLevelDynamicResourceDocs(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetAllBankLevelDynamicResourceDocs = CanGetAllBankLevelDynamicResourceDocs()
|
||||
|
||||
case class CanDeleteBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteBankLevelDynamicResourceDoc = CanDeleteBankLevelDynamicResourceDoc()
|
||||
|
||||
case class CanCreateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canCreateDynamicMessageDoc = CanCreateDynamicMessageDoc()
|
||||
|
||||
case class CanCreateBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canCreateBankLevelDynamicMessageDoc = CanCreateBankLevelDynamicMessageDoc()
|
||||
|
||||
case class CanUpdateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateDynamicMessageDoc = CanUpdateDynamicMessageDoc()
|
||||
@ -762,12 +780,18 @@ object ApiRole {
|
||||
case class CanGetDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetDynamicMessageDoc = CanGetDynamicMessageDoc()
|
||||
|
||||
case class CanGetBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canGetBankLevelDynamicMessageDoc = CanGetBankLevelDynamicMessageDoc()
|
||||
|
||||
case class CanGetAllDynamicMessageDocs(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllDynamicMessageDocs = CanGetAllDynamicMessageDocs()
|
||||
|
||||
case class CanDeleteDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canDeleteDynamicMessageDoc = CanDeleteDynamicMessageDoc()
|
||||
|
||||
case class CanDeleteBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteBankLevelDynamicMessageDoc = CanDeleteBankLevelDynamicMessageDoc()
|
||||
|
||||
case class CanCreateEndpointMapping(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canCreateEndpointMapping = CanCreateEndpointMapping()
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ object CustomJsonFormats {
|
||||
|
||||
val formats: Formats = JsonSerializers.commonFormats + PrimaryDataBodySerializer + ToStringDeSerializer + TupleSerializer
|
||||
|
||||
|
||||
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless ++ JsonSerializers.serializers
|
||||
|
||||
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) ++ JsonSerializers.serializers
|
||||
|
||||
@ -1,16 +1,43 @@
|
||||
package code.api.util
|
||||
import com.openbankproject.commons.util.JsonUtils
|
||||
import code.api.JsonResponseException
|
||||
import code.api.util.APIUtil.ResourceDoc
|
||||
import code.api.util.ErrorMessages.DynamicResourceDocMethodDependency
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.v4_0_0.JSONFactory400
|
||||
import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
|
||||
import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
|
||||
import com.openbankproject.commons.ExecutionContext
|
||||
import com.openbankproject.commons.model.BankId
|
||||
import com.openbankproject.commons.util.Functions.Memo
|
||||
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
import javassist.{ClassPool, LoaderClassPath}
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import net.liftweb.json.{JObject, JValue, prettyRender}
|
||||
import net.liftweb.http.JsonResponse
|
||||
import net.liftweb.json.{ JValue, prettyRender}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.lang.reflect.ReflectPermission
|
||||
import java.net.NetPermission
|
||||
import java.security.{AccessControlContext, AccessController, CodeSource, Permission, PermissionCollection, Permissions, Policy, PrivilegedAction, ProtectionDomain}
|
||||
import java.util.PropertyPermission
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import scala.collection.immutable.List
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.reflect.runtime.universe
|
||||
import scala.reflect.runtime.universe.runtimeMirror
|
||||
import scala.runtime.NonLocalReturnControl
|
||||
import scala.tools.reflect.{ToolBox, ToolBoxError}
|
||||
|
||||
object DynamicUtil {
|
||||
|
||||
val toolBox: ToolBox[universe.type] = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
||||
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
|
||||
}
|
||||
|
||||
// code -> dynamic method function
|
||||
// the same code should always be compiled once, so here cache them
|
||||
@ -115,6 +142,81 @@ object DynamicUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicCodeDependentMethods(clazz: Class[_], predicate: String => Boolean = _ => true): List[(String, String, String)] = {
|
||||
val className = clazz.getTypeName
|
||||
val listBuffer = new ListBuffer[(String, String, String)]()
|
||||
for {
|
||||
method <- getClassPool(clazz.getClassLoader).get(className).getDeclaredMethods.toList
|
||||
if predicate(method.getName)
|
||||
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.getDependentMethods(typeName, methodName, signature))
|
||||
} else {
|
||||
listBuffer.append(ternary)
|
||||
}
|
||||
}
|
||||
|
||||
listBuffer.distinct.toList
|
||||
}
|
||||
|
||||
trait Sandbox {
|
||||
@throws[Exception]
|
||||
def runInSandbox[R](action: => R): R
|
||||
}
|
||||
|
||||
object Sandbox {
|
||||
// initialize SecurityManager if not initialized
|
||||
if (System.getSecurityManager == null) {
|
||||
Policy.setPolicy(new Policy() {
|
||||
override def getPermissions(codeSource: CodeSource): PermissionCollection = {
|
||||
for (element <- Thread.currentThread.getStackTrace) {
|
||||
if ("sun.rmi.server.LoaderHandler" == element.getClassName && "loadClass" == element.getMethodName)
|
||||
return new Permissions
|
||||
}
|
||||
super.getPermissions(codeSource)
|
||||
}
|
||||
|
||||
override def implies(domain: ProtectionDomain, permission: Permission) = true
|
||||
})
|
||||
System.setSecurityManager(new SecurityManager)
|
||||
}
|
||||
|
||||
def createSandbox(permissionList: List[Permission]): Sandbox = {
|
||||
val accessControlContext: AccessControlContext = {
|
||||
val permissions = new Permissions()
|
||||
permissionList.foreach(permissions.add)
|
||||
val protectionDomain = new ProtectionDomain(null, permissions)
|
||||
new AccessControlContext(Array(protectionDomain))
|
||||
}
|
||||
|
||||
new Sandbox {
|
||||
@throws[Exception]
|
||||
def runInSandbox[R](action: => R): R = try {
|
||||
val privilegedAction: PrivilegedAction[R] = () => action
|
||||
|
||||
AccessController.doPrivileged(privilegedAction, accessControlContext)
|
||||
} catch {
|
||||
case e: NonLocalReturnControl[Full[JsonResponse]] if e.value.isInstanceOf[Full[JsonResponse]] =>
|
||||
throw JsonResponseException(e.value.orNull)
|
||||
|
||||
case e: NonLocalReturnControl[JsonResponse] if e.value.isInstanceOf[JsonResponse] =>
|
||||
throw JsonResponseException(e.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val memoSandbox = new Memo[String, Sandbox]
|
||||
|
||||
/**
|
||||
* this method will call create Sandbox underneath, but will have default permissions and bankId permission and cache.
|
||||
*/
|
||||
def sandbox(bankId: String): Sandbox = memoSandbox.memoize(bankId) {
|
||||
Sandbox.createSandbox(BankId.permission(bankId) :: Validation.allowedRuntimePermissions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* common import statements those are used by compiler
|
||||
*/
|
||||
@ -163,5 +265,99 @@ object DynamicUtil {
|
||||
|import scala.concurrent.duration._
|
||||
|import scala.concurrent.{Await, Future}
|
||||
|import com.openbankproject.commons.dto._
|
||||
|import code.api.util.APIUtil.ResourceDoc
|
||||
|import code.api.util.DynamicUtil.Sandbox
|
||||
|import code.api.util.NewStyle.HttpCode
|
||||
|import code.api.util._
|
||||
|import code.api.v4_0_0.JSONFactory400
|
||||
|import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
|
||||
|import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
|
||||
|import com.openbankproject.commons.ExecutionContext
|
||||
|import com.openbankproject.commons.model.BankId
|
||||
|import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
|import net.liftweb.common.{Box, Full}
|
||||
|import org.apache.commons.lang3.StringUtils
|
||||
|
|
||||
|import java.io.File
|
||||
|import java.security.{AccessControlException, Permission}
|
||||
|import java.util.PropertyPermission
|
||||
|import scala.collection.immutable.List
|
||||
|import scala.io.Source
|
||||
|""".stripMargin
|
||||
|
||||
|
||||
object Validation {
|
||||
|
||||
val dynamicCodeSandboxPermissions = APIUtil.getPropsValue("dynamic_code_sandbox_permissions", "[]").trim
|
||||
val scalaCodePermissioins = "List[java.security.Permission]"+dynamicCodeSandboxPermissions.replaceFirst("\\[","(").dropRight(1)+")"
|
||||
val permissions:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode(scalaCodePermissioins)
|
||||
|
||||
// all Permissions put at here
|
||||
// Here is the Java Permission document, please extend these permissions carefully.
|
||||
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc3.html#17001
|
||||
// If you are not familiar with the permissions, we provide the clear error messages for the missing permissions in the log.
|
||||
// eg1 scala test level : and have a look at the scala test for `createSandbox` method, you can see how to add permissions there too.
|
||||
// eg2 api level: "OBP-40047: DynamicResourceDoc method have no enough permissions. No permission of: (\"java.io.FilePermission\" \"stop-words-en.txt\" \"write\")"
|
||||
// --> you can extends following permission: new java.net.SocketPermission("ir.dcs.gla.ac.uk:80", "connect,resolve"),
|
||||
// NOTE: These permissions are only checked during runtime, not the compilation period.
|
||||
val allowedRuntimePermissions = permissions.openOrThrowException("Can not compile the props `dynamic_code_sandbox_permissions` to permissions")
|
||||
|
||||
val dependenciesString = APIUtil.getPropsValue("dynamic_code_compile_validate_dependencies", "[]").trim
|
||||
val scalaCodeDependencies = s"${DynamicUtil.importStatements}"+dependenciesString.replaceFirst("\\[","Map(").dropRight(1) +").mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)"
|
||||
val dependenciesBox: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(scalaCodeDependencies)
|
||||
|
||||
/**
|
||||
* Compilation OBP Dependencies Guard, only checked the OBP methods, not scala/Java libraies(are checked during the runtime.).
|
||||
*
|
||||
* allowedCompilationMethods -->
|
||||
* The following methods will be checked when you call the `Create Dynamic ResourceDoc/MessageDoc` endpoints.
|
||||
* You can control all the OBP methods here.
|
||||
*/
|
||||
// all allowed methods put at here, typeName -> methods
|
||||
val allowedCompilationMethods: Map[String, Set[String]] = dependenciesBox.openOrThrowException("Can not compile the props `dynamic_code_compile_validate_dependencies` to Map")
|
||||
|
||||
//Do not touch this Set, try to use the `allowedPermissions` and `allowedMethods` to control the sandbox
|
||||
val restrictedTypes = Set(
|
||||
"scala.reflect.runtime.",
|
||||
"java.lang.reflect.",
|
||||
"scala.concurrent.ExecutionContext"
|
||||
)
|
||||
|
||||
private def isRestrictedType(typeName: String) = ReflectUtils.isObpClass(typeName) || restrictedTypes.exists(typeName.startsWith)
|
||||
|
||||
/**
|
||||
* validate dependencies, (className, methodName, signature)
|
||||
*
|
||||
* Here only validate the restricted types(isObpClass + val restrictedTypes), not all scala/java types.
|
||||
*/
|
||||
private def validateDependency(dependentMethods: List[(String, String, String)]) = {
|
||||
val notAllowedDependentMethods = dependentMethods collect {
|
||||
case (typeName, method, _)
|
||||
if isRestrictedType(typeName) &&
|
||||
!allowedCompilationMethods.get(typeName).exists(set => set.contains(method) || set.contains("*")) &&
|
||||
!allowedCompilationMethods.exists { it =>
|
||||
val (tpName, allowedMethods) = it
|
||||
tpName.endsWith("*") &&
|
||||
typeName.startsWith(StringUtils.substringBeforeLast(tpName, "*")) &&
|
||||
(allowedMethods.contains(method) || allowedMethods.contains("*"))
|
||||
}
|
||||
=>
|
||||
s"$typeName.$method"
|
||||
}
|
||||
// change to JsonResponseException
|
||||
if(notAllowedDependentMethods.nonEmpty) {
|
||||
val illegalDependency = notAllowedDependentMethods.mkString("[", ", ", "]")
|
||||
throw JsonResponseException(s"$DynamicResourceDocMethodDependency $illegalDependency", 400, "none")
|
||||
}
|
||||
}
|
||||
|
||||
def validateDependency(obj: AnyRef): Unit = {
|
||||
if(APIUtil.getPropsAsBoolValue("dynamic_code_compile_validate_enable",false)){
|
||||
val dependentMethods: List[(String, String, String)] = DynamicUtil.getDynamicCodeDependentMethods(obj.getClass)
|
||||
validateDependency(dependentMethods)
|
||||
} else{ // If false, nothing to do here.
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,7 +544,11 @@ object ErrorMessages {
|
||||
val DynamicMessageDocNotFound = "OBP-40043: DynamicMessageDoc not found, please specify valid DYNAMIC_MESSAGE_DOC_ID. "
|
||||
val DynamicMessageDocDeleteError = "OBP-40044: DynamicMessageDoc can not be deleted. "
|
||||
val DynamicCodeCompileFail = "OBP-40045: The code to do compile is illegal scala code, compilation failed. "
|
||||
val InvalidOperationId = "OBP-40046: Invalid operation_id, please specify valid operation_id."
|
||||
|
||||
val DynamicResourceDocMethodDependency = "OBP-40046: DynamicResourceDoc method call forbidden methods. "
|
||||
val DynamicResourceDocMethodPermission = "OBP-40047: DynamicResourceDoc method have no enough permissions. "
|
||||
|
||||
val InvalidOperationId = "OBP-40048: Invalid operation_id, please specify valid operation_id."
|
||||
// Exceptions (OBP-50XXX)
|
||||
val UnknownError = "OBP-50000: Unknown Error."
|
||||
val FutureTimeoutException = "OBP-50001: Future Timeout Exception."
|
||||
|
||||
@ -397,21 +397,23 @@ object ExampleValue {
|
||||
lazy val connectorMethodIdExample = ConnectorField("ace0352a-9a0f-4bfa-b30b-9003aa467f51", "A string that MUST uniquely identify the connector method on this OBP instance, can be used in all cache. ")
|
||||
glossaryItems += makeGlossaryItem("ConnectorMethod.connectorMethodId", connectorMethodIdExample)
|
||||
|
||||
lazy val dynamicResourceDocMethodBodyExample = ConnectorField("%20%20%20%20val%20Some(resourceDoc)%20%3D%20callContext.resourceDocument%0A%20%20%20%20" +
|
||||
"val%20hasRequestBody%20%3D%20request.body.isDefined%0A%0A%20%20%20%20%2F%2F%20get%20Path%20Parameters%2C%20example%3A%0A%20%20%20" +
|
||||
"%20%2F%2F%20if%20the%20requestUrl%20of%20resourceDoc%20is%20%2Fhello%2Fbanks%2FBANK_ID%2Fworld%0A%20%20%20%20%2F%2F%20the%20reque" +
|
||||
"st%20path%20is%20%2Fhello%2Fbanks%2Fbank_x%2Fworld%0A%20%20%20%20%2F%2FpathParams.get(%22BANK_ID%22)%20will%20get%20Option(%22bank" +
|
||||
"_x%22)%20value%0A%20%20%20%20val%20pathParams%20%3D%20getPathParams(callContext%2C%20request)%0A%20%20%20%20val%20myUserId%20%3D%2" +
|
||||
"0pathParams(%22MY_USER_ID%22)%0A%0A%0A%20%20%20%20val%20requestEntity%20%3D%20request.json%20match%20%7B%0A%20%20%20%20%20%20case%" +
|
||||
"20Full(zson)%20%3D%3E%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20zson.extract%5BRequestRootJsonClass%5D%0" +
|
||||
"A%20%20%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20%20%20case%20e%3A%20MappingException%20%3D%3E%0A%20%20%20%20%" +
|
||||
"20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidJsonFormat%20%24%7Be.msg%7D%22))%0A%20%20%20%20%20%20%20%20%7" +
|
||||
"D%0A%20%20%20%20%20%20case%20_%3A%20EmptyBox%20%3D%3E%0A%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidRequ" +
|
||||
"estPayload%20Current%20request%20has%20no%20payload%22))%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%2F%2F%20please%20add%20business%20" +
|
||||
"logic%20here%0A%20%20%20%20val%20responseBody%3AResponseRootJsonClass%20%3D%20ResponseRootJsonClass(s%22%24%7BmyUserId%7D_from_pat" +
|
||||
"h%22%2C%20requestEntity.name%2C%20requestEntity.age%2C%20requestEntity.hobby)%0A%20%20%20%20Future.successful%20%7B%0A%20%20%20%20" +
|
||||
"%20%20(responseBody%2C%20HttpCode.%60200%60(callContext.callContext))%0A%20%20%20%20%7D",
|
||||
lazy val dynamicResourceDocMethodBodyExample = ConnectorField("%20%20%20%20val%20Some(resourceDoc)%20%3D%20callContext." +
|
||||
"resourceDocument%0A%20%20%20%20val%20hasRequestBody%20%3D%20request.body.isDefined%0A%0A%20%20%20%20%2F%2F%20get%20" +
|
||||
"Path%20Parameters%2C%20example%3A%0A%20%20%20%20%2F%2F%20if%20the%20requestUrl%20of%20resourceDoc%20is%20%2Fhello%2" +
|
||||
"Fbanks%2FBANK_ID%2Fworld%0A%20%20%20%20%2F%2F%20the%20request%20path%20is%20%2Fhello%2Fbanks%2Fbank_x%2Fworld%0A%20" +
|
||||
"%20%20%20%2F%2FpathParams.get(%22BANK_ID%22)%20will%20get%20Option(%22bank_x%22)%20value%0A%0A%20%20%20%20val%20my" +
|
||||
"UserId%20%3D%20pathParams(%22MY_USER_ID%22)%0A%0A%0A%20%20%20%20val%20requestEntity%20%3D%20request.json%20match%20" +
|
||||
"%7B%0A%20%20%20%20%20%20case%20Full(zson)%20%3D%3E%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%" +
|
||||
"20%20zson.extract%5BRequestRootJsonClass%5D%0A%20%20%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20%" +
|
||||
"20%20case%20e%3A%20MappingException%20%3D%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(" +
|
||||
"s%22%24InvalidJsonFormat%20%24%7Be.msg%7D%22))%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20case%20_%3A%20Emp" +
|
||||
"tyBox%20%3D%3E%0A%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidRequestPayload%20Current%20" +
|
||||
"request%20has%20no%20payload%22))%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%2F%2F%20please%20add%20business%20logic%20" +
|
||||
"here%0A%20%20%20%20val%20responseBody%3AResponseRootJsonClass%20%3D%20ResponseRootJsonClass(s%22%24%7BmyUserId%7D_" +
|
||||
"from_path%22%2C%20requestEntity.name%2C%20requestEntity.age%2C%20requestEntity.hobby)%0A%20%20%20%20Future.successf" +
|
||||
"ul%20%7B%0A%20%20%20%20%20%20(responseBody%2C%20HttpCode.%60200%60(callContext.callContext))%0A%20%20%20%20%7D",
|
||||
"the URL-encoded format String, the original code is the OBP connector method body.")
|
||||
|
||||
glossaryItems += makeGlossaryItem("DynamicResourceDoc.methodBody", dynamicResourceDocMethodBodyExample)
|
||||
|
||||
lazy val connectorMethodBodyExample = ConnectorField("%20%20%20%20%20%20Future.successful%28%0A%20%20%20%20%20%20%20%20Full%28%" +
|
||||
@ -1612,6 +1614,9 @@ object ExampleValue {
|
||||
lazy val nameExample = ConnectorField("ACCOUNT_MANAGEMENT_FEE",NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("name", nameExample)
|
||||
|
||||
lazy val ageExample = ConnectorField("18", "The user age.")
|
||||
glossaryItems += makeGlossaryItem("age", nameExample)
|
||||
|
||||
lazy val productFeeIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
|
||||
glossaryItems += makeGlossaryItem("product_fee_id", nameExample)
|
||||
|
||||
|
||||
@ -616,6 +616,13 @@ object Glossary extends MdcLoggable {
|
||||
|"""
|
||||
)
|
||||
|
||||
glossaryItems += GlossaryItem(
|
||||
title =
|
||||
"Age",
|
||||
description =
|
||||
"""The user Age"""
|
||||
)
|
||||
|
||||
glossaryItems += GlossaryItem(
|
||||
title = "Account.account_id",
|
||||
description =
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package code.api.util
|
||||
|
||||
import java.io
|
||||
|
||||
import java.util.Date
|
||||
import java.util.UUID.randomUUID
|
||||
|
||||
@ -9,7 +9,7 @@ import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
|
||||
import code.api.APIFailureNewStyle
|
||||
import code.api.Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID
|
||||
import code.api.cache.Caching
|
||||
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
|
||||
import code.api.util.APIUtil.{EntitlementAndScopeStatus, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
|
||||
import code.api.util.ApiRole.canCreateAnyTransactionRequest
|
||||
import code.api.util.ErrorMessages.{InsufficientAuthorisationToCreateTransactionRequest, _}
|
||||
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
|
||||
@ -54,6 +54,7 @@ import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.json.{JField, JInt, JNothing, JNull, JObject, JString, JValue, _}
|
||||
import net.liftweb.util.Helpers.tryo
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.security.AccessControlException
|
||||
|
||||
import scala.collection.immutable.List
|
||||
import scala.concurrent.Future
|
||||
@ -63,7 +64,6 @@ import code.validation.{JsonSchemaValidationProvider, JsonValidation}
|
||||
import net.liftweb.http.JsonResponse
|
||||
import net.liftweb.util.Props
|
||||
import code.api.JsonResponseException
|
||||
import code.api.v4_0_0.ProductFeeJsonV400
|
||||
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityInfo}
|
||||
import code.bankattribute.BankAttribute
|
||||
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
|
||||
@ -71,9 +71,6 @@ import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
|
||||
import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc}
|
||||
import code.endpointMapping.{EndpointMappingProvider, EndpointMappingT}
|
||||
import code.endpointTag.EndpointTagT
|
||||
import net.liftweb.json
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
object NewStyle {
|
||||
lazy val endpoints: List[(String, String)] = List(
|
||||
@ -191,6 +188,18 @@ object NewStyle {
|
||||
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
|
||||
private def validateBankId(bankId: Option[String], callContext: Option[CallContext]): Unit = {
|
||||
bankId.foreach(validateBankId(_, callContext))
|
||||
}
|
||||
|
||||
private def validateBankId(bankId: String, callContext: Option[CallContext]): Unit = try {
|
||||
BankId.checkPermission(bankId)
|
||||
} catch {
|
||||
case _: AccessControlException =>
|
||||
val correlationId = callContext.map(_.correlationId).getOrElse("none")
|
||||
throw JsonResponseException(s"$DynamicResourceDocMethodPermission No permission of operate bank $bankId", 400, correlationId)
|
||||
}
|
||||
|
||||
def getBranch(bankId : BankId, branchId : BranchId, callContext: Option[CallContext]): OBPReturnType[BranchT] = {
|
||||
Connector.connector.vend.getBranch(bankId, branchId, callContext) map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(BranchNotFoundByBranchId, 400, callContext.map(_.toLight)))
|
||||
@ -292,13 +301,17 @@ object NewStyle {
|
||||
}
|
||||
|
||||
def createBankLevelEndpointTag(bankId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createBankLevelEndpointTag(bankId, operationId, tagName, callContext) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$CreateEndpointTagError", 400), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def updateBankLevelEndpointTag(bankId:String, endpointTagId: String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
|
||||
Connector.connector.vend.updateBankLevelEndpointTag(bankId, endpointTagId, operationId, tagName, callContext) map {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.updateBankLevelEndpointTag(bankId, endpointTagId, operationId, tagName, callContext) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$UpdateEndpointTagError", 400), i._2)
|
||||
}
|
||||
}
|
||||
@ -310,6 +323,8 @@ object NewStyle {
|
||||
}
|
||||
|
||||
def checkBankLevelEndpointTagExists(bankId: String, operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.getBankLevelEndpointTag(bankId: String, operationId: String, tagName: String, callContext) map {
|
||||
i => (i._1.isDefined, i._2)
|
||||
}
|
||||
@ -334,6 +349,8 @@ object NewStyle {
|
||||
}
|
||||
|
||||
def getBankLevelEndpointTags(bankId:String, operationId : String, callContext: Option[CallContext]) : OBPReturnType[List[EndpointTagT]] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.getBankLevelEndpointTags(bankId, operationId, callContext) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponseForGetEndpointTags Current OPERATION_ID is $operationId", 404), i._2)
|
||||
}
|
||||
@ -364,6 +381,7 @@ object NewStyle {
|
||||
bankRoutingScheme: String,
|
||||
bankRoutingAddress: String,
|
||||
callContext: Option[CallContext]): OBPReturnType[Bank] = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
Connector.connector.vend.createOrUpdateBank(
|
||||
bankId,
|
||||
@ -936,7 +954,7 @@ object NewStyle {
|
||||
}
|
||||
|
||||
def hasEntitlement(bankId: String, userId: String, role: ApiRole, callContext: Option[CallContext], errorMsg: String = ""): Future[Box[Unit]] = {
|
||||
val errorInfo =
|
||||
val errorInfo =
|
||||
if(StringUtils.isBlank(errorMsg)&& !bankId.isEmpty) UserHasMissingRoles + role.toString() + s" at Bank($bankId)"
|
||||
else if(StringUtils.isBlank(errorMsg)&& bankId.isEmpty) UserHasMissingRoles + role.toString()
|
||||
else errorMsg
|
||||
@ -1903,6 +1921,8 @@ object NewStyle {
|
||||
def getProductCollectionItemsTree(collectionCode: String,
|
||||
bankId: String,
|
||||
callContext: Option[CallContext]): OBPReturnType[List[(ProductCollectionItem, Product, List[ProductAttribute])]] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.getProductCollectionItemsTree(collectionCode, bankId, callContext) map {
|
||||
i => {
|
||||
val data: Box[List[ProductCollectionItemsTree]] = i._1
|
||||
@ -2032,6 +2052,8 @@ object NewStyle {
|
||||
mSatisfied: Boolean,
|
||||
comments: String,
|
||||
callContext: Option[CallContext]): OBPReturnType[KycCheck] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createOrUpdateKycCheck(bankId, customerId, id, customerNumber, date, how, staffUserId, mStaffName, mSatisfied, comments, callContext)
|
||||
.map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse Can not create or update KycCheck in the backend. ", 400), i._2)
|
||||
@ -2048,6 +2070,8 @@ object NewStyle {
|
||||
issuePlace: String,
|
||||
expiryDate: Date,
|
||||
callContext: Option[CallContext]): OBPReturnType[KycDocument] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createOrUpdateKycDocument(
|
||||
bankId,
|
||||
customerId,
|
||||
@ -2074,6 +2098,8 @@ object NewStyle {
|
||||
relatesToKycDocumentId: String,
|
||||
relatesToKycCheckId: String,
|
||||
callContext: Option[CallContext]): OBPReturnType[KycMedia] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createOrUpdateKycMedia(
|
||||
bankId,
|
||||
customerId,
|
||||
@ -2096,6 +2122,8 @@ object NewStyle {
|
||||
ok: Boolean,
|
||||
date: Date,
|
||||
callContext: Option[CallContext]): OBPReturnType[KycStatus] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createOrUpdateKycStatus(
|
||||
bankId,
|
||||
customerId,
|
||||
@ -2299,7 +2327,9 @@ object NewStyle {
|
||||
posted: Option[CardPostedInfo],
|
||||
customerId: String,
|
||||
callContext: Option[CallContext]
|
||||
): OBPReturnType[PhysicalCard] =
|
||||
): OBPReturnType[PhysicalCard] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createPhysicalCard(
|
||||
bankCardNumber: String,
|
||||
nameOnCard: String,
|
||||
@ -2325,6 +2355,7 @@ object NewStyle {
|
||||
) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$CreateCardError"), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def updatePhysicalCard(
|
||||
cardId: String,
|
||||
@ -2349,7 +2380,9 @@ object NewStyle {
|
||||
posted: Option[CardPostedInfo],
|
||||
customerId: String,
|
||||
callContext: Option[CallContext]
|
||||
): OBPReturnType[PhysicalCardTrait] =
|
||||
): OBPReturnType[PhysicalCardTrait] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.updatePhysicalCard(
|
||||
cardId: String,
|
||||
bankCardNumber: String,
|
||||
@ -2376,7 +2409,8 @@ object NewStyle {
|
||||
) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, s"$UpdateCardError"), i._2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def getPhysicalCardsForBank(bank: Bank, user : User, queryParams: List[OBPQueryParam], callContext:Option[CallContext]) : OBPReturnType[List[PhysicalCard]] =
|
||||
Connector.connector.vend.getPhysicalCardsForBank(bank: Bank, user : User, queryParams: List[OBPQueryParam], callContext:Option[CallContext]) map {
|
||||
i => (unboxFullOrFail(i._1, callContext, CardNotFound), i._2)
|
||||
@ -2583,19 +2617,27 @@ object NewStyle {
|
||||
}
|
||||
}
|
||||
|
||||
def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future {
|
||||
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = Future {
|
||||
(EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
(EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def getEndpointMappingById(bankId: Option[String], endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(bankId, endpointMappingId)
|
||||
Future{
|
||||
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByEndpointMappingId Current ENDPOINT_MAPPING_ID is $endpointMappingId", 404)
|
||||
@ -2604,6 +2646,8 @@ object NewStyle {
|
||||
}
|
||||
|
||||
def getEndpointMappingByOperationId(bankId: Option[String], operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(bankId, operationId)
|
||||
Future{
|
||||
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByOperationId Current OPERATION_ID is $operationId",404)
|
||||
@ -2616,6 +2660,8 @@ object NewStyle {
|
||||
def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = {
|
||||
import scala.concurrent.duration._
|
||||
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(endpointMappingTTL second) {
|
||||
@ -2674,24 +2720,29 @@ object NewStyle {
|
||||
* @param dynamicEntityId
|
||||
* @return
|
||||
*/
|
||||
def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = Future {
|
||||
for {
|
||||
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
|
||||
deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity)
|
||||
deleteEntitleMentResult <- if(deleteEntityResult) {
|
||||
Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId)
|
||||
} else {
|
||||
Box !! false
|
||||
def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = {
|
||||
validateBankId(bankId, None)
|
||||
Future {
|
||||
for {
|
||||
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
|
||||
deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity)
|
||||
deleteEntitleMentResult <- if (deleteEntityResult) {
|
||||
Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId)
|
||||
} else {
|
||||
Box !! false
|
||||
}
|
||||
} yield {
|
||||
if (deleteEntitleMentResult) {
|
||||
DynamicEntityInfo.roleNames(entity.entityName, entity.bankId).foreach(ApiRole.removeDynamicApiRole(_))
|
||||
}
|
||||
deleteEntitleMentResult
|
||||
}
|
||||
} yield {
|
||||
if(deleteEntitleMentResult) {
|
||||
DynamicEntityInfo.roleNames(entity.entityName, entity.bankId).foreach(ApiRole.removeDynamicApiRole(_))
|
||||
}
|
||||
deleteEntitleMentResult
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicEntityById(bankId: Option[String], dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
|
||||
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404)
|
||||
Future{
|
||||
@ -2699,9 +2750,12 @@ object NewStyle {
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future {
|
||||
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
|
||||
(boxedDynamicEntity, callContext)
|
||||
def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
|
||||
(boxedDynamicEntity, callContext)
|
||||
}
|
||||
}
|
||||
|
||||
private[this] val dynamicEntityTTL = {
|
||||
@ -2712,6 +2766,8 @@ object NewStyle {
|
||||
def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] = {
|
||||
import scala.concurrent.duration._
|
||||
|
||||
validateBankId(bankId, None)
|
||||
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) {
|
||||
@ -2766,6 +2822,8 @@ object NewStyle {
|
||||
queryParameters: Option[Map[String, List[String]]],
|
||||
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
|
||||
import DynamicEntityOperation._
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
|
||||
// do validate, any validate process fail will return immediately
|
||||
if(dynamicEntityBox.isEmpty) {
|
||||
@ -2848,6 +2906,8 @@ object NewStyle {
|
||||
dateStarts: Date,
|
||||
dateExpires: Option[Date],
|
||||
callContext: Option[CallContext]): OBPReturnType[DirectDebitTrait] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createDirectDebit(
|
||||
bankId,
|
||||
accountId,
|
||||
@ -2875,6 +2935,8 @@ object NewStyle {
|
||||
dateStarts: Date,
|
||||
dateExpires: Option[Date],
|
||||
callContext: Option[CallContext]): OBPReturnType[StandingOrderTrait] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.createStandingOrder(
|
||||
bankId,
|
||||
accountId,
|
||||
@ -2920,19 +2982,27 @@ object NewStyle {
|
||||
getConnectorByName(connectorName).flatMap(_.callableMethods.get(methodName))
|
||||
}
|
||||
|
||||
def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId:Option[String], userId, swaggerString), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId: Option[String], userId, swaggerString), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext)
|
||||
} map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId)
|
||||
val errorMessage =
|
||||
if(bankId.isEmpty)
|
||||
@ -2945,8 +3015,11 @@ object NewStyle {
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext)
|
||||
def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = {
|
||||
validateBankId(bankId, callContext)
|
||||
Future {
|
||||
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext)
|
||||
}
|
||||
}
|
||||
|
||||
def getDynamicEndpointsByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
|
||||
@ -2959,6 +3032,8 @@ object NewStyle {
|
||||
* @return
|
||||
*/
|
||||
def deleteDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(bankId, dynamicEndpointId, callContext)
|
||||
for {
|
||||
(entity, _) <- dynamicEndpoint
|
||||
@ -3256,89 +3331,91 @@ object NewStyle {
|
||||
(unboxFullOrFail(connectorMethod, callContext, s"$ConnectorMethodNotFound Current CONNECTOR_METHOD_ID(${connectorMethodId})", 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicResourceDocExists(requestVerb : String, requestUrl : String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def isJsonDynamicResourceDocExists(bankId: Option[String], requestVerb : String, requestUrl : String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicResourceDocProvider.provider.vend.getByVerbAndUrl(requestVerb, requestUrl)
|
||||
val result = DynamicResourceDocProvider.provider.vend.getByVerbAndUrl(bankId, requestVerb, requestUrl)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def createJsonDynamicResourceDoc(dynamicResourceDoc: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
def createJsonDynamicResourceDoc(bankId: Option[String], dynamicResourceDoc: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val newInternalConnector = DynamicResourceDocProvider.provider.vend.create(dynamicResourceDoc)
|
||||
val newInternalConnector = DynamicResourceDocProvider.provider.vend.create(bankId, dynamicResourceDoc)
|
||||
val errorMsg = s"$UnknownError Can not create Dynamic Resource Doc in the backend. "
|
||||
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def updateJsonDynamicResourceDoc(entity: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
def updateJsonDynamicResourceDoc(bankId: Option[String], entity: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val updatedConnectorMethod = DynamicResourceDocProvider.provider.vend.update(entity: JsonDynamicResourceDoc)
|
||||
val updatedConnectorMethod = DynamicResourceDocProvider.provider.vend.update(bankId, entity: JsonDynamicResourceDoc)
|
||||
val errorMsg = s"$UnknownError Can not update Dynamic Resource Doc in the backend. "
|
||||
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicResourceDocExists(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def isJsonDynamicResourceDocExists(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
|
||||
val result = DynamicResourceDocProvider.provider.vend.getById(bankId, dynamicResourceDocId)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def getJsonDynamicResourceDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicResourceDoc]] =
|
||||
def getJsonDynamicResourceDocs(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicResourceDoc]] =
|
||||
Future {
|
||||
val dynamicResourceDocs: List[JsonDynamicResourceDoc] = DynamicResourceDocProvider.provider.vend.getAll()
|
||||
val dynamicResourceDocs: List[JsonDynamicResourceDoc] = DynamicResourceDocProvider.provider.vend.getAll(bankId)
|
||||
dynamicResourceDocs -> callContext
|
||||
}
|
||||
|
||||
def getJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
def getJsonDynamicResourceDocById(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
|
||||
Future {
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.getById(bankId, dynamicResourceDocId)
|
||||
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def deleteJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def deleteJsonDynamicResourceDocById(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.deleteById(dynamicResourceDocId)
|
||||
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.deleteById(bankId, dynamicResourceDocId)
|
||||
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocDeleteError Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def createJsonDynamicMessageDoc(dynamicMessageDoc: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
def createJsonDynamicMessageDoc(bankId: Option[String], dynamicMessageDoc: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val newInternalConnector = DynamicMessageDocProvider.provider.vend.create(dynamicMessageDoc)
|
||||
val newInternalConnector = DynamicMessageDocProvider.provider.vend.create(bankId, dynamicMessageDoc)
|
||||
val errorMsg = s"$UnknownError Can not create Dynamic Message Doc in the backend. "
|
||||
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def updateJsonDynamicMessageDoc(entity: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
def updateJsonDynamicMessageDoc(bankId: Option[String], entity: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val updatedConnectorMethod = DynamicMessageDocProvider.provider.vend.update(entity: JsonDynamicMessageDoc)
|
||||
val updatedConnectorMethod = DynamicMessageDocProvider.provider.vend.update(bankId: Option[String], entity: JsonDynamicMessageDoc)
|
||||
val errorMsg = s"$UnknownError Can not update Dynamic Message Doc in the backend. "
|
||||
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
|
||||
}
|
||||
|
||||
def isJsonDynamicMessageDocExists(process: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def isJsonDynamicMessageDocExists(bankId: Option[String], process: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val result = DynamicMessageDocProvider.provider.vend.getByProcess(process)
|
||||
val result = DynamicMessageDocProvider.provider.vend.getByProcess(bankId, process)
|
||||
(result.isDefined, callContext)
|
||||
}
|
||||
|
||||
def getJsonDynamicMessageDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicMessageDoc]] =
|
||||
def getJsonDynamicMessageDocs(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicMessageDoc]] =
|
||||
Future {
|
||||
val dynamicMessageDocs: List[JsonDynamicMessageDoc] = DynamicMessageDocProvider.provider.vend.getAll()
|
||||
val dynamicMessageDocs: List[JsonDynamicMessageDoc] = DynamicMessageDocProvider.provider.vend.getAll(bankId)
|
||||
dynamicMessageDocs -> callContext
|
||||
}
|
||||
|
||||
def getJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
def getJsonDynamicMessageDocById(bankId: Option[String], dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
|
||||
Future {
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.getById(dynamicMessageDocId)
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.getById(bankId, dynamicMessageDocId)
|
||||
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicMessageDocId})", 400), callContext)
|
||||
}
|
||||
|
||||
def deleteJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
def deleteJsonDynamicMessageDocById(bankId: Option[String], dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
|
||||
Future {
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.deleteById(dynamicMessageDocId)
|
||||
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.deleteById(bankId, dynamicMessageDocId)
|
||||
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocDeleteError", 400), callContext)
|
||||
}
|
||||
|
||||
def validateUserAuthContextUpdateRequest(bankId: String, userId: String, key: String, value: String, scaMethod: String, callContext: Option[CallContext]): OBPReturnType[UserAuthContextUpdate] = {
|
||||
validateBankId(bankId, callContext)
|
||||
|
||||
Connector.connector.vend.validateUserAuthContextUpdateRequest(bankId, userId, key, value, scaMethod, callContext) map {
|
||||
i => (connectorEmptyResponse(i._1, callContext), i._2)
|
||||
}
|
||||
|
||||
@ -7,10 +7,11 @@ import java.util.{Calendar, Date}
|
||||
import code.DynamicData.{DynamicData, DynamicDataProvider}
|
||||
import code.DynamicEndpoint.DynamicEndpointSwagger
|
||||
import code.accountattribute.AccountAttributeX
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, _}
|
||||
import code.api.util.APIUtil.{fullBoxOrException, _}
|
||||
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, _}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.DynamicUtil.Validation
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.ExampleValue._
|
||||
import code.api.util.Glossary.getGlossaryItem
|
||||
@ -9238,7 +9239,7 @@ trait APIMethods400 {
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(connectorMethod, callContext) <- NewStyle.function.createJsonConnectorMethod(jsonConnectorMethod, callContext)
|
||||
} yield {
|
||||
(connectorMethod, HttpCode.`201`(callContext))
|
||||
@ -9283,6 +9284,7 @@ trait APIMethods400 {
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(connectorMethod, callContext) <- NewStyle.function.updateJsonConnectorMethod(connectorMethodId, connectorMethodBody.methodBody, callContext)
|
||||
} yield {
|
||||
(connectorMethod, HttpCode.`200`(callContext))
|
||||
@ -9395,18 +9397,21 @@ trait APIMethods400 {
|
||||
}
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
.validateDependency()
|
||||
} catch {
|
||||
case e: JsonResponseException =>
|
||||
throw e
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
|
||||
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(None, jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicResourceDocAlreadyExists The combination of request_url(${jsonDynamicResourceDoc.requestUrl}) and request_verb(${jsonDynamicResourceDoc.requestVerb}) must be unique", cc=callContext) {
|
||||
(!isExists)
|
||||
}
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(jsonDynamicResourceDoc, callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(None, jsonDynamicResourceDoc, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
@ -9459,15 +9464,18 @@ trait APIMethods400 {
|
||||
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
.validateDependency()
|
||||
} catch {
|
||||
case e: JsonResponseException =>
|
||||
throw e
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(None, dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
@ -9498,8 +9506,8 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(dynamicResourceDocId, callContext)
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(None, dynamicResourceDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
@ -9530,7 +9538,7 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
@ -9561,13 +9569,253 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-resource-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(cc.callContext)
|
||||
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(None, cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-resource-docs", dynamicResourceDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createBankLevelDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(createBankLevelDynamicResourceDoc),
|
||||
"POST",
|
||||
"/management/banks/BANK_ID/dynamic-resource-docs",
|
||||
"Create Bank Level Dynamic Resource Doc",
|
||||
s"""Create a Bank Level Dynamic Resource Doc.
|
||||
|
|
||||
|The connector_method_body is URL-encoded format String
|
||||
|""",
|
||||
jsonDynamicResourceDoc.copy(dynamicResourceDocId=None),
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canCreateBankLevelDynamicResourceDoc)))
|
||||
|
||||
lazy val createBankLevelDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
jsonDynamicResourceDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicResourceDoc]
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""", cc=cc.callContext) {
|
||||
Set("POST", "PUT", "GET", "DELETE").contains(jsonDynamicResourceDoc.requestVerb)
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String "" or just totally omit the field""", cc=cc.callContext) {
|
||||
(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.exampleRequestBody) match {
|
||||
case ("GET" | "DELETE", Some(JString(s))) => //we support the empty string "" here
|
||||
StringUtils.isBlank(s)
|
||||
case ("GET" | "DELETE", Some(requestBody)) => //we add the guard, we forbid any json objects in GET/DELETE request body.
|
||||
requestBody == JNothing
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
.validateDependency()
|
||||
} catch {
|
||||
case e: JsonResponseException =>
|
||||
throw e
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
_ = try {
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(Some(bankId), jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicResourceDocAlreadyExists The combination of request_url(${jsonDynamicResourceDoc.requestUrl}) and request_verb(${jsonDynamicResourceDoc.requestVerb}) must be unique", cc=callContext) {
|
||||
(!isExists)
|
||||
}
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(Some(bankId), jsonDynamicResourceDoc, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateBankLevelDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateBankLevelDynamicResourceDoc),
|
||||
"PUT",
|
||||
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Update Bank Level Dynamic Resource Doc",
|
||||
s"""Update a Bank Level Dynamic Resource Doc.
|
||||
|
|
||||
|The connector_method_body is URL-encoded format String
|
||||
|""",
|
||||
jsonDynamicResourceDoc.copy(dynamicResourceDocId = None),
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canUpdateBankLevelDynamicResourceDoc)))
|
||||
|
||||
lazy val updateBankLevelDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonPut json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicResourceDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicResourceDoc]
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""", cc=cc.callContext) {
|
||||
Set("POST", "PUT", "GET", "DELETE").contains(dynamicResourceDocBody.requestVerb)
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String""", cc=cc.callContext) {
|
||||
(dynamicResourceDocBody.requestVerb, dynamicResourceDocBody.exampleRequestBody) match {
|
||||
case ("GET" | "DELETE", Some(JString(s))) =>
|
||||
StringUtils.isBlank(s)
|
||||
case ("GET" | "DELETE", Some(requestBody)) =>
|
||||
requestBody == JNothing
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
_ = try {
|
||||
CompiledObjects(dynamicResourceDocBody.exampleRequestBody, dynamicResourceDocBody.successResponseBody, dynamicResourceDocBody.methodBody)
|
||||
.validateDependency()
|
||||
} catch {
|
||||
case e: JsonResponseException =>
|
||||
throw e
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
_ = try {
|
||||
CompiledObjects(dynamicResourceDocBody.exampleRequestBody, dynamicResourceDocBody.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
|
||||
throw JsonResponseException(jsonResponse)
|
||||
}
|
||||
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
|
||||
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(Some(bankId), dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteBankLevelDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteBankLevelDynamicResourceDoc),
|
||||
"DELETE",
|
||||
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Delete Bank Level Dynamic Resource Doc",
|
||||
s"""Delete a Bank Level Dynamic Resource Doc.
|
||||
|""",
|
||||
EmptyBody,
|
||||
BooleanBody(true),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canDeleteBankLevelDynamicResourceDoc)))
|
||||
|
||||
lazy val deleteBankLevelDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getBankLevelDynamicResourceDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(getBankLevelDynamicResourceDoc),
|
||||
"GET",
|
||||
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
|
||||
"Get Bank Level Dynamic Resource Doc by Id",
|
||||
s"""Get a Bank Level Dynamic Resource Doc by DYNAMIC-RESOURCE-DOC-ID.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
jsonDynamicResourceDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canGetBankLevelDynamicResourceDoc)))
|
||||
|
||||
lazy val getBankLevelDynamicResourceDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAllBankLevelDynamicResourceDocs,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAllBankLevelDynamicResourceDocs),
|
||||
"GET",
|
||||
"/management/banks/BANK_ID/dynamic-resource-docs",
|
||||
"Get all Bank Level Dynamic Resource Docs",
|
||||
s"""Get all Bank Level Dynamic Resource Docs.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
ListResult("dynamic-resource-docs", jsonDynamicResourceDoc::Nil),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicResourceDoc, apiTagNewStyle),
|
||||
Some(List(canGetAllBankLevelDynamicResourceDocs)))
|
||||
|
||||
lazy val getAllBankLevelDynamicResourceDocs: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(Some(bankId), cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-resource-docs", dynamicResourceDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
buildDynamicEndpointTemplate,
|
||||
@ -9647,7 +9895,7 @@ trait APIMethods400 {
|
||||
dynamicMessageDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicMessageDoc]
|
||||
}
|
||||
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(dynamicMessageDoc.process, cc.callContext)
|
||||
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(None, dynamicMessageDoc.process, cc.callContext)
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicMessageDocAlreadyExists The json body process(${dynamicMessageDoc.process}) already exists", cc=callContext) {
|
||||
(!dynamicMessageDocExisted)
|
||||
}
|
||||
@ -9656,7 +9904,53 @@ trait APIMethods400 {
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(dynamicMessageDoc, callContext)
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(None, dynamicMessageDoc, callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createBankLevelDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(createBankLevelDynamicMessageDoc),
|
||||
"POST",
|
||||
"/management/banks/BANK_ID/dynamic-message-docs",
|
||||
"Create Bank Level Dynamic Message Doc",
|
||||
s"""Create a Bank Level Dynamic Message Doc.
|
||||
|""",
|
||||
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canCreateBankLevelDynamicMessageDoc)))
|
||||
|
||||
lazy val createBankLevelDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId ::"dynamic-message-docs" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicMessageDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicMessageDoc]
|
||||
}
|
||||
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(Some(bankId), dynamicMessageDoc.process, cc.callContext)
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$DynamicMessageDocAlreadyExists The json body process(${dynamicMessageDoc.process}) already exists", cc=callContext) {
|
||||
(!dynamicMessageDocExisted)
|
||||
}
|
||||
connectorMethod = DynamicConnector.createFunction(dynamicMessageDoc.process, dynamicMessageDoc.decodedMethodBody)
|
||||
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(Some(bankId), dynamicMessageDoc, callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`201`(callContext))
|
||||
}
|
||||
@ -9695,8 +9989,9 @@ trait APIMethods400 {
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=cc.callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(None, dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
@ -9727,7 +10022,7 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
@ -9758,7 +10053,7 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-message-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(cc.callContext)
|
||||
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(None, cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-message-docs", dynamicMessageDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
@ -9789,8 +10084,147 @@ trait APIMethods400 {
|
||||
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(dynamicMessageDocId, callContext)
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(None, dynamicMessageDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateBankLevelDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateBankLevelDynamicMessageDoc),
|
||||
"PUT",
|
||||
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
|
||||
"Update Bank Level Dynamic Message Doc",
|
||||
s"""Update a Bank Level Dynamic Message Doc.
|
||||
|""",
|
||||
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canUpdateDynamicMessageDoc)))
|
||||
|
||||
lazy val updateBankLevelDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId::"dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonPut json -> _ => {
|
||||
cc =>
|
||||
for {
|
||||
dynamicMessageDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
|
||||
json.extract[JsonDynamicMessageDoc]
|
||||
}
|
||||
connectorMethod = DynamicConnector.createFunction(dynamicMessageDocBody.process, dynamicMessageDocBody.decodedMethodBody)
|
||||
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
|
||||
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=cc.callContext) {
|
||||
connectorMethod.isDefined
|
||||
}
|
||||
_ = Validation.validateDependency(connectorMethod.orNull)
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, cc.callContext)
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(Some(bankId), dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getBankLevelDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(getBankLevelDynamicMessageDoc),
|
||||
"GET",
|
||||
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
|
||||
"Get Bank Level Dynamic Message Doc",
|
||||
s"""Get a Bank Level Dynamic Message Doc by DYNAMIC_MESSAGE_DOC_ID.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
jsonDynamicMessageDoc,
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canGetBankLevelDynamicMessageDoc)))
|
||||
|
||||
lazy val getBankLevelDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
|
||||
} yield {
|
||||
(dynamicMessageDoc, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAllBankLevelDynamicMessageDocs,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAllBankLevelDynamicMessageDocs),
|
||||
"GET",
|
||||
"/management/banks/BANK_ID/dynamic-message-docs",
|
||||
"Get all Bank Level Dynamic Message Docs",
|
||||
s"""Get all Bank Level Dynamic Message Docs.
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
ListResult("dynamic-message-docs", jsonDynamicMessageDoc::Nil),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canGetAllDynamicMessageDocs)))
|
||||
|
||||
lazy val getAllBankLevelDynamicMessageDocs: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(Some(bankId), cc.callContext)
|
||||
} yield {
|
||||
(ListResult("dynamic-message-docs", dynamicMessageDocs), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteBankLevelDynamicMessageDoc,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteBankLevelDynamicMessageDoc),
|
||||
"DELETE",
|
||||
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
|
||||
"Delete Bank Level Dynamic Message Doc",
|
||||
s"""Delete a Bank Level Dynamic Message Doc.
|
||||
|""",
|
||||
EmptyBody,
|
||||
BooleanBody(true),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagDynamicMessageDoc, apiTagNewStyle),
|
||||
Some(List(canDeleteBankLevelDynamicMessageDoc)))
|
||||
|
||||
lazy val deleteBankLevelDynamicMessageDoc: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, cc.callContext)
|
||||
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, callContext)
|
||||
} yield {
|
||||
(dynamicResourceDoc, HttpCode.`204`(callContext))
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.APIUtil.{OBPEndpoint, OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture}
|
||||
import code.api.util.{CallContext, CustomJsonFormats}
|
||||
import code.api.util.DynamicUtil.{Sandbox, Validation}
|
||||
import code.api.util.{CallContext, CustomJsonFormats, DynamicUtil}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
|
||||
@ -12,19 +13,34 @@ import net.liftweb.http.{JsonResponse, Req}
|
||||
trait DynamicCompileEndpoint {
|
||||
implicit val formats = CustomJsonFormats.formats
|
||||
|
||||
protected def process(callContext: CallContext, request: Req): Box[JsonResponse]
|
||||
// * is any bankId
|
||||
val boundBankId: String
|
||||
|
||||
protected def process(callContext: CallContext, request: Req, pathParams: Map[String, String]): Box[JsonResponse]
|
||||
|
||||
val endpoint: OBPEndpoint = new OBPEndpoint {
|
||||
override def isDefinedAt(x: Req): Boolean = true
|
||||
|
||||
override def apply(request: Req): CallContext => Box[JsonResponse] = process(_, request)
|
||||
override def apply(request: Req): CallContext => Box[JsonResponse] = { cc =>
|
||||
val Some(pathParams) = cc.resourceDocument.map(_.getPathParams(request.path.partPath))
|
||||
|
||||
validateDependencies()
|
||||
|
||||
Sandbox.sandbox(boundBankId).runInSandbox {
|
||||
process(cc, request, pathParams)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
|
||||
futureToBoxedResponse(scalaFutureToLaFuture(scf))
|
||||
}
|
||||
protected def getPathParams(callContext: CallContext, request: Req): Map[String, String] = {
|
||||
val Some(resourceDoc) = callContext.resourceDocument
|
||||
resourceDoc.getPathParams(request.path.partPath)
|
||||
private def validateDependencies() = {
|
||||
val dependencies = DynamicUtil.getDynamicCodeDependentMethods(this.getClass, "process" == )
|
||||
Validation.validateDependency(dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
object DynamicCompileEndpoint {
|
||||
implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
|
||||
futureToBoxedResponse(scalaFutureToLaFuture(scf))
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package code.api.v4_0_0.dynamic
|
||||
|
||||
import code.api.util.DynamicUtil.{Sandbox, Validation}
|
||||
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, OBPEndpoint, PrimaryDataBody, ResourceDoc, StringBody, getDisabledEndpointOperationIds}
|
||||
import code.api.util.{CallContext, DynamicUtil}
|
||||
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
|
||||
@ -125,7 +125,7 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
|
||||
}
|
||||
val successResponse: Product = toCaseObject(successResponseBody)
|
||||
|
||||
val partialFunction: OBPEndpoint = {
|
||||
private val partialFunction: OBPEndpoint = {
|
||||
|
||||
//If the requestBody is PrimaryDataBody, return None. otherwise, return the exampleRequestBody:Option[JValue]
|
||||
// In side OBP resourceDoc, requestBody and successResponse must be Product type,
|
||||
@ -148,26 +148,36 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
|
||||
|
||||
val code =
|
||||
s"""
|
||||
|import code.api.util.APIUtil.errorJsonResponse
|
||||
|import code.api.util.CallContext
|
||||
|import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|
||||
|import code.api.util.NewStyle.HttpCode
|
||||
|import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
|
||||
|import code.api.util.APIUtil.{OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture, errorJsonResponse}
|
||||
|
|
||||
|import net.liftweb.common.{Box, EmptyBox, Full}
|
||||
|import net.liftweb.http.{JsonResponse, Req}
|
||||
|import net.liftweb.json.MappingException
|
||||
|
|
||||
|import scala.concurrent.Future
|
||||
|import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
|
|
||||
|implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
|
||||
| futureToBoxedResponse(scalaFutureToLaFuture(scf))
|
||||
|}
|
||||
|
|
||||
|implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
|
|
||||
|$requestBodyCaseClasses
|
||||
|
|
||||
|$responseBodyCaseClasses
|
||||
|
|
||||
|(new DynamicCompileEndpoint {
|
||||
| override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
|
||||
| $decodedMethodBody
|
||||
| }
|
||||
|}).endpoint
|
||||
|val endpoint: code.api.util.APIUtil.OBPEndpoint = {
|
||||
| case request => { callContext =>
|
||||
| val Some(pathParams) = callContext.resourceDocument.map(_.getPathParams(request.path.partPath))
|
||||
| $decodedMethodBody
|
||||
| }
|
||||
|}
|
||||
|
|
||||
|endpoint
|
||||
|
|
||||
|""".stripMargin
|
||||
val endpointMethod = DynamicUtil.compileScalaCode[OBPEndpoint](code)
|
||||
@ -180,6 +190,37 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this will check all the dynamic scala code dependencies at compile time.
|
||||
*
|
||||
*Search for the usage, you can see how to use it in OBP code.
|
||||
*/
|
||||
def validateDependency() = Validation.validateDependency(this.partialFunction)
|
||||
|
||||
/**
|
||||
* This is used to check the security permission at the run time.
|
||||
* all the obp partialFunctions will be wrapped into the sandbox which under the permission control.
|
||||
*
|
||||
*/
|
||||
def sandboxEndpoint(bankId: Option[String]) : OBPEndpoint = {
|
||||
val sandbox = bankId match {
|
||||
case Some(v) if StringUtils.isNotBlank(v) =>
|
||||
Sandbox.sandbox(v)
|
||||
case _ => Sandbox.sandbox("*")
|
||||
}
|
||||
|
||||
new OBPEndpoint {
|
||||
override def isDefinedAt(req: Req): Boolean = partialFunction.isDefinedAt(req)
|
||||
|
||||
// run dynamic code in sandbox
|
||||
override def apply(req: Req): CallContext => Box[JsonResponse] = {cc =>
|
||||
val fn = partialFunction.apply(req)
|
||||
|
||||
sandbox.runInSandbox(fn(cc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def toCaseObject(jValue: Option[JValue]): Product = {
|
||||
if (jValue.isEmpty || jValue.exists(JNothing ==)) {
|
||||
EmptyBody
|
||||
@ -195,4 +236,3 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ object DynamicResourceDocsEndpointGroup extends EndpointGroup {
|
||||
|
||||
|
||||
override protected def resourceDocs: List[APIUtil.ResourceDoc] =
|
||||
DynamicResourceDocProvider.provider.vend.getAllAndConvert(toResourceDoc)
|
||||
DynamicResourceDocProvider.provider.vend.getAllAndConvert(None, toResourceDoc) //TODO need to check if this can be `NONE`
|
||||
|
||||
private val apiVersion : ScannedApiVersion = ApiVersion.v4_0_0
|
||||
|
||||
@ -37,7 +37,7 @@ object DynamicResourceDocsEndpointGroup extends EndpointGroup {
|
||||
private val toResourceDoc: JsonDynamicResourceDoc => ResourceDoc = { dynamicDoc =>
|
||||
val compiledObjects = CompiledObjects(dynamicDoc.exampleRequestBody, dynamicDoc.successResponseBody, dynamicDoc.methodBody)
|
||||
ResourceDoc(
|
||||
partialFunction = compiledObjects.partialFunction, //connectorMethodBody
|
||||
partialFunction = compiledObjects.sandboxEndpoint(dynamicDoc.bankId),
|
||||
implementedInApiVersion = apiVersion,
|
||||
partialFunctionName = dynamicDoc.partialFunctionName + "_" + (dynamicDoc.requestVerb + dynamicDoc.requestUrl).hashCode,
|
||||
requestVerb = dynamicDoc.requestVerb,
|
||||
|
||||
@ -1,35 +1,55 @@
|
||||
package code.api.v4_0_0.dynamic.practise
|
||||
|
||||
import code.api.util.APIUtil.errorJsonResponse
|
||||
import code.api.util.CallContext
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
|
||||
import net.liftweb.common.{Box, EmptyBox, Full}
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
import net.liftweb.json.MappingException
|
||||
|
||||
import scala.concurrent.Future
|
||||
// any import statement here need be moved into the process method body
|
||||
|
||||
/**
|
||||
* practise new endpoint at this object, don't commit you practise code to git
|
||||
*
|
||||
* This endpoint is only for testing new dynamic resource/messages method body.
|
||||
* eg: when you try the create dynamic resource doc endpoint, you need to prepare the method body properly.
|
||||
* you can prepare the obp scala code just under the method:
|
||||
* `process(callContext: CallContext, request: Req, pathParams: Map[String, String])`,
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
object PractiseEndpoint extends DynamicCompileEndpoint {
|
||||
// all request case classes
|
||||
case class RequestRootJsonClass(name: String, age: Long, hobby: Option[List[String]])
|
||||
object PractiseEndpoint extends code.api.v4_0_0.dynamic.DynamicCompileEndpoint {
|
||||
// don't modify these import statement
|
||||
import code.api.util.CallContext
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.util.APIUtil.{OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture, errorJsonResponse}
|
||||
|
||||
import net.liftweb.common.{Box, EmptyBox, Full}
|
||||
import net.liftweb.http.{JsonResponse, Req}
|
||||
import net.liftweb.json.MappingException
|
||||
|
||||
// all response case classes
|
||||
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: Option[List[String]])
|
||||
import scala.concurrent.Future
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint._
|
||||
|
||||
|
||||
// request method
|
||||
val requestMethod = "POST"
|
||||
val requestUrl = "/my_user/MY_USER_ID"
|
||||
|
||||
|
||||
// all request case classes
|
||||
case class RequestRootJsonClass(name: String, age: Long, hobby: List[String])
|
||||
|
||||
|
||||
// all response case classes
|
||||
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: List[String])
|
||||
|
||||
// * is any bankId, if bound to other bankId, just modify this value to correct one
|
||||
override val boundBankId = "abc"
|
||||
|
||||
// copy the whole method body as "dynamicResourceDoc" method body
|
||||
override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
|
||||
override protected def
|
||||
process(callContext: CallContext, request: Req, pathParams: Map[String, String]) : Box[JsonResponse] = {
|
||||
// please add import sentences here, those used by this method
|
||||
import code.api.util.NewStyle
|
||||
import code.api.v4_0_0.JSONFactory400
|
||||
|
||||
val Some(resourceDoc) = callContext.resourceDocument
|
||||
val hasRequestBody = request.body.isDefined
|
||||
@ -38,14 +58,12 @@ object PractiseEndpoint extends DynamicCompileEndpoint {
|
||||
// if the requestUrl of resourceDoc is /hello/banks/BANK_ID/world
|
||||
// the request path is /hello/banks/bank_x/world
|
||||
//pathParams.get("BANK_ID") will get Option("bank_x") value
|
||||
val pathParams = getPathParams(callContext, request)
|
||||
val myUserId = pathParams("MY_USER_ID")
|
||||
|
||||
|
||||
val requestEntity = request.json match {
|
||||
case Full(zson) =>
|
||||
try {
|
||||
zson.extract[RequestRootJsonClass]
|
||||
zson.extract[RequestRootJsonClass]
|
||||
} catch {
|
||||
case e: MappingException =>
|
||||
return Full(errorJsonResponse(s"$InvalidJsonFormat ${e.msg}"))
|
||||
@ -53,12 +71,13 @@ object PractiseEndpoint extends DynamicCompileEndpoint {
|
||||
case _: EmptyBox =>
|
||||
return Full(errorJsonResponse(s"$InvalidRequestPayload Current request has no payload"))
|
||||
}
|
||||
|
||||
|
||||
// please add business logic here
|
||||
val responseBody:ResponseRootJsonClass = ResponseRootJsonClass(s"${myUserId}_from_path", requestEntity.name, requestEntity.age, requestEntity.hobby)
|
||||
Future.successful {
|
||||
(responseBody, HttpCode.`200`(callContext.callContext))
|
||||
val responseBody:ResponseRootJsonClass = ResponseRootJsonClass(s"${myUserId}_from_path", requestEntity.name, requestEntity.age, requestEntity.hobby)
|
||||
|
||||
for {
|
||||
(banks, callContext) <- NewStyle.function.getBanks(Some(callContext))
|
||||
} yield {
|
||||
(JSONFactory400.createBanksJson(banks), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package code.api.v4_0_0.dynamic.practise
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.requestRootJsonClass
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.APIUtil.{ResourceDoc, StringBody}
|
||||
import code.api.util.ApiTag.{apiTagDynamicResourceDoc, apiTagNewStyle}
|
||||
@ -24,10 +25,18 @@ object PractiseEndpointGroup extends EndpointGroup{
|
||||
PractiseEndpoint.requestUrl,
|
||||
"A test endpoint",
|
||||
s"""A test endpoint.
|
||||
|Just for debug method body of dynamic resource doc
|
||||
|
|
||||
|Just for debug method body of dynamic resource doc.
|
||||
|better watch the following introduction video first
|
||||
|* [Dynamic resourceDoc version1](https://vimeo.com/623381607)
|
||||
|
|
||||
|The endpoint return the response from PractiseEndpoint code.
|
||||
|Here, code.api.v4_0_0.dynamic.practise.PractiseEndpoint.process
|
||||
|You can test the method body grammar, and try the business logic, but need to restart the OBP-API code .
|
||||
|
|
||||
|""",
|
||||
StringBody("Any request body"),
|
||||
StringBody("Any response body"),
|
||||
requestRootJsonClass,
|
||||
requestRootJsonClass,
|
||||
List(
|
||||
UnknownError
|
||||
),
|
||||
|
||||
@ -4,9 +4,8 @@ import code.api.util.DynamicUtil.compileScalaCode
|
||||
import code.api.util.{CallContext, DynamicUtil}
|
||||
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
|
||||
import net.liftweb.common.Box
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
|
||||
import scala.concurrent.Future
|
||||
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType}
|
||||
|
||||
|
||||
object DynamicConnector {
|
||||
@ -23,14 +22,13 @@ object DynamicConnector {
|
||||
def updateSingletonObject(key:String, value: Any) = singletonObjectMap.update(key, value)
|
||||
def removeSingletonObject(key:String) = singletonObjectMap.remove(key)
|
||||
|
||||
def invoke(process: String, args: Array[AnyRef], callContext: Option[CallContext]) = {
|
||||
val function: Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]] =
|
||||
getFunction(process).asInstanceOf[Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]]]
|
||||
def invoke(bankId: Option[String], process: String, args: Array[AnyRef], callContext: Option[CallContext]) = {
|
||||
val function = getFunction(bankId, process)
|
||||
function.map(f =>f(args: Array[AnyRef], callContext: Option[CallContext])).openOrThrowException(s"There is no process $process, it should not be called here")
|
||||
}
|
||||
}
|
||||
|
||||
private def getFunction(process: String) = {
|
||||
DynamicMessageDocProvider.provider.vend.getByProcess(process) map {
|
||||
private def getFunction(bankId: Option[String], process: String) = {
|
||||
DynamicMessageDocProvider.provider.vend.getByProcess(bankId, process) map {
|
||||
case v :JsonDynamicMessageDoc =>
|
||||
createFunction(process, v.decodedMethodBody).openOrThrowException(s"InternalConnector method compile fail")
|
||||
}
|
||||
@ -44,17 +42,20 @@ object DynamicConnector {
|
||||
* @param methodBody method body of connector method
|
||||
* @return function of connector method that is dynamic created, can be Function0, Function1, Function2...
|
||||
*/
|
||||
def createFunction(process: String, methodBody:String): Box[Any] =
|
||||
def createFunction(process: String, methodBody:String): Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]] =
|
||||
{
|
||||
//messageDoc.process is a bit different with the methodName, we need tweak the format of it:
|
||||
//eg: process("obp.getBank") ==> methodName("getBank")
|
||||
val method = s"""
|
||||
|${DynamicUtil.importStatements}
|
||||
|def func(args: Array[AnyRef], callContext: Option[CallContext]): Future[Box[(AnyRef, Option[CallContext])]] = {
|
||||
| $methodBody
|
||||
|
|
||||
|val fn = new ((Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]) (){
|
||||
| override def apply(args: Array[AnyRef], callContext: Option[CallContext]): Future[Box[(AnyRef, Option[CallContext])]] = {
|
||||
| $methodBody
|
||||
| }
|
||||
|}
|
||||
|
|
||||
|func _
|
||||
|fn
|
||||
|""".stripMargin
|
||||
compileScalaCode(method)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ object InternalConnector {
|
||||
* @param methodBody method body of connector method
|
||||
* @return function of connector method that is dynamic created, can be Function0, Function1, Function2...
|
||||
*/
|
||||
def createFunction(methodName: String, methodBody:String): Box[Any] =
|
||||
def createFunction(methodName: String, methodBody:String)=
|
||||
methodNameToSignature.get(methodName) match {
|
||||
case Some(signature) =>
|
||||
val method = s"""
|
||||
|
||||
@ -9,6 +9,7 @@ class DynamicMessageDoc extends LongKeyedMapper[DynamicMessageDoc] with IdPK {
|
||||
|
||||
override def getSingleton = DynamicMessageDoc
|
||||
|
||||
object BankId extends MappedString(this, 255)
|
||||
object DynamicMessageDocId extends UUIDString(this)
|
||||
object Process extends MappedString(this, 255)
|
||||
object MessageFormat extends MappedString(this, 255)
|
||||
@ -27,6 +28,7 @@ class DynamicMessageDoc extends LongKeyedMapper[DynamicMessageDoc] with IdPK {
|
||||
object DynamicMessageDoc extends DynamicMessageDoc with LongKeyedMetaMapper[DynamicMessageDoc] {
|
||||
override def dbIndexes: List[BaseIndex[DynamicMessageDoc]] = UniqueIndex(DynamicMessageDocId) :: UniqueIndex(Process) :: super.dbIndexes
|
||||
def getJsonDynamicMessageDoc(dynamicMessageDoc: DynamicMessageDoc) = JsonDynamicMessageDoc(
|
||||
bankId = Some(dynamicMessageDoc.BankId.get),
|
||||
dynamicMessageDocId = Some(dynamicMessageDoc.DynamicMessageDocId.get),
|
||||
process = dynamicMessageDoc.Process.get,
|
||||
messageFormat = dynamicMessageDoc.MessageFormat.get,
|
||||
|
||||
@ -16,6 +16,7 @@ object DynamicMessageDocProvider extends SimpleInjector {
|
||||
}
|
||||
|
||||
case class JsonDynamicMessageDoc(
|
||||
bankId: Option[String],
|
||||
dynamicMessageDocId: Option[String],
|
||||
process: String,
|
||||
messageFormat: String,
|
||||
@ -34,12 +35,12 @@ case class JsonDynamicMessageDoc(
|
||||
|
||||
trait DynamicMessageDocProvider {
|
||||
|
||||
def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc]
|
||||
def getByProcess(process: String): Box[JsonDynamicMessageDoc]
|
||||
def getAll(): List[JsonDynamicMessageDoc]
|
||||
def getById(bankId: Option[String], dynamicMessageDocId: String): Box[JsonDynamicMessageDoc]
|
||||
def getByProcess(bankId: Option[String], process: String): Box[JsonDynamicMessageDoc]
|
||||
def getAll(bankId: Option[String]): List[JsonDynamicMessageDoc]
|
||||
|
||||
def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def deleteById(dynamicMessageDocId: String): Box[Boolean]
|
||||
def create(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def update(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
|
||||
def deleteById(bankId: Option[String], dynamicMessageDocId: String): Box[Boolean]
|
||||
|
||||
}
|
||||
@ -7,6 +7,7 @@ import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers.tryo
|
||||
import net.liftweb.util.Props
|
||||
|
||||
import java.util.UUID.randomUUID
|
||||
import code.util.Helper
|
||||
|
||||
@ -19,27 +20,43 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
|
||||
else APIUtil.getPropsValue(s"dynamicMessageDoc.cache.ttl.seconds", "40").toInt
|
||||
}
|
||||
|
||||
override def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc] =
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId))
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
override def getById(bankId: Option[String], dynamicMessageDocId: String): Box[JsonDynamicMessageDoc] =
|
||||
if(bankId.isEmpty) {
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}else{
|
||||
DynamicMessageDoc.find(
|
||||
By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId),
|
||||
By(DynamicMessageDoc.BankId, bankId.getOrElse("")
|
||||
)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}
|
||||
|
||||
override def getByProcess(process: String): Box[JsonDynamicMessageDoc] =
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.Process, process))
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
override def getByProcess(bankId: Option[String], process: String): Box[JsonDynamicMessageDoc] =
|
||||
if(bankId.isEmpty) {
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.Process, process)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}else{
|
||||
DynamicMessageDoc.find(
|
||||
By(DynamicMessageDoc.Process, process),
|
||||
By(DynamicMessageDoc.BankId, bankId.getOrElse("")
|
||||
)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}
|
||||
|
||||
|
||||
override def getAll(): List[JsonDynamicMessageDoc] = {
|
||||
override def getAll(bankId: Option[String]): List[JsonDynamicMessageDoc] = {
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicMessageDocTTL second) {
|
||||
DynamicMessageDoc.findAll()
|
||||
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
if(bankId.isEmpty){
|
||||
DynamicMessageDoc.findAll().map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
} else {
|
||||
DynamicMessageDoc.findAll(By(DynamicMessageDoc.BankId, bankId.getOrElse(""))).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
override def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]=
|
||||
override def create(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]= {
|
||||
tryo {
|
||||
DynamicMessageDoc.create
|
||||
.BankId(bankId.getOrElse(null))
|
||||
.DynamicMessageDocId(APIUtil.generateUUID())
|
||||
.Process(entity.process)
|
||||
.MessageFormat(entity.messageFormat)
|
||||
@ -54,10 +71,20 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
|
||||
.MethodBody(entity.methodBody)
|
||||
.saveMe()
|
||||
}.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
|
||||
}
|
||||
|
||||
|
||||
override def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc] = {
|
||||
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))) match {
|
||||
override def update(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc] = {
|
||||
val dynamicMessageDocBox = if(bankId.isDefined){
|
||||
DynamicMessageDoc.find(
|
||||
By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))
|
||||
)
|
||||
} else {
|
||||
DynamicMessageDoc.find(
|
||||
By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))
|
||||
)
|
||||
}
|
||||
dynamicMessageDocBox match {
|
||||
case Full(v) =>
|
||||
tryo {
|
||||
v.DynamicMessageDocId(entity.dynamicMessageDocId.getOrElse(""))
|
||||
@ -78,7 +105,14 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
|
||||
}
|
||||
}
|
||||
|
||||
override def deleteById(id: String): Box[Boolean] = tryo {
|
||||
DynamicMessageDoc.bulkDelete_!!(By(DynamicMessageDoc.DynamicMessageDocId, id))
|
||||
override def deleteById(bankId: Option[String], id: String): Box[Boolean] = tryo {
|
||||
if(bankId.isEmpty) {
|
||||
DynamicMessageDoc.bulkDelete_!!(By(DynamicMessageDoc.DynamicMessageDocId, id))
|
||||
}else{
|
||||
DynamicMessageDoc.bulkDelete_!!(
|
||||
By(DynamicMessageDoc.BankId, bankId.getOrElse("")),
|
||||
By(DynamicMessageDoc.DynamicMessageDocId, id)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,8 @@ import scala.collection.immutable.List
|
||||
class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK {
|
||||
|
||||
override def getSingleton = DynamicResourceDoc
|
||||
|
||||
|
||||
object BankId extends MappedString(this, 255)
|
||||
object DynamicResourceDocId extends UUIDString(this)
|
||||
object PartialFunctionName extends MappedString(this, 255)
|
||||
object RequestVerb extends MappedString(this, 255)
|
||||
@ -30,6 +31,7 @@ class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK {
|
||||
object DynamicResourceDoc extends DynamicResourceDoc with LongKeyedMetaMapper[DynamicResourceDoc] {
|
||||
override def dbIndexes: List[BaseIndex[DynamicResourceDoc]] = UniqueIndex(DynamicResourceDocId) :: UniqueIndex(RequestUrl,RequestVerb) :: super.dbIndexes
|
||||
def getJsonDynamicResourceDoc(dynamicResourceDoc: DynamicResourceDoc) = JsonDynamicResourceDoc(
|
||||
bankId = Some(dynamicResourceDoc.BankId.get),
|
||||
dynamicResourceDocId = Some(dynamicResourceDoc.DynamicResourceDocId.get),
|
||||
methodBody = dynamicResourceDoc.MethodBody.get,
|
||||
partialFunctionName = dynamicResourceDoc.PartialFunctionName.get,
|
||||
|
||||
@ -20,6 +20,7 @@ object DynamicResourceDocProvider extends SimpleInjector {
|
||||
}
|
||||
|
||||
case class JsonDynamicResourceDoc(
|
||||
bankId: Option[String],
|
||||
dynamicResourceDocId: Option[String],
|
||||
methodBody: String,
|
||||
partialFunctionName: String,
|
||||
@ -38,15 +39,15 @@ case class JsonDynamicResourceDoc(
|
||||
|
||||
trait DynamicResourceDocProvider {
|
||||
|
||||
def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc]
|
||||
def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc]
|
||||
def getById(bankId: Option[String], dynamicResourceDocId: String): Box[JsonDynamicResourceDoc]
|
||||
def getByVerbAndUrl(bankId: Option[String], requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc]
|
||||
|
||||
def getAll(): List[JsonDynamicResourceDoc] = getAllAndConvert(identity)
|
||||
def getAll(bankId: Option[String]): List[JsonDynamicResourceDoc] = getAllAndConvert(bankId, identity)
|
||||
|
||||
def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T]
|
||||
def getAllAndConvert[T: Manifest](bankId: Option[String], transform: JsonDynamicResourceDoc => T): List[T]
|
||||
|
||||
def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def deleteById(dynamicResourceDocId: String): Box[Boolean]
|
||||
def create(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def update(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
|
||||
def deleteById(bankId: Option[String], dynamicResourceDocId: String): Box[Boolean]
|
||||
|
||||
}
|
||||
|
||||
@ -20,29 +20,58 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
|
||||
else APIUtil.getPropsValue(s"dynamicResourceDoc.cache.ttl.seconds", "40").toInt
|
||||
}
|
||||
|
||||
override def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
override def getById(bankId: Option[String], dynamicResourceDocId: String): Box[JsonDynamicResourceDoc] = {
|
||||
if(bankId.isEmpty){
|
||||
DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
} else{
|
||||
DynamicResourceDoc
|
||||
.find(
|
||||
By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId),
|
||||
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
|
||||
)
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
}
|
||||
}
|
||||
|
||||
override def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.RequestVerb, requestVerb), By(DynamicResourceDoc.RequestUrl, requestUrl))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
override def getByVerbAndUrl(bankId: Option[String], requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc] =
|
||||
if(bankId.isEmpty){
|
||||
DynamicResourceDoc
|
||||
.find(By(DynamicResourceDoc.RequestVerb, requestVerb), By(DynamicResourceDoc.RequestUrl, requestUrl))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
} else{
|
||||
DynamicResourceDoc
|
||||
.find(
|
||||
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
|
||||
By(DynamicResourceDoc.RequestVerb, requestVerb),
|
||||
By(DynamicResourceDoc.RequestUrl, requestUrl))
|
||||
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
}
|
||||
|
||||
override def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T] = {
|
||||
override def getAllAndConvert[T: Manifest](bankId: Option[String], transform: JsonDynamicResourceDoc => T): List[T] = {
|
||||
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
|
||||
CacheKeyFromArguments.buildCacheKey {
|
||||
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocTTL second) {
|
||||
DynamicResourceDoc.findAll()
|
||||
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
|
||||
}}
|
||||
if(bankId.isEmpty){
|
||||
DynamicResourceDoc.findAll()
|
||||
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
|
||||
} else {
|
||||
DynamicResourceDoc.findAll(
|
||||
By(DynamicResourceDoc.BankId, bankId.getOrElse("")))
|
||||
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]=
|
||||
override def create(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]=
|
||||
tryo {
|
||||
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
|
||||
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
|
||||
|
||||
DynamicResourceDoc.create
|
||||
.BankId(bankId.getOrElse(null))
|
||||
.DynamicResourceDocId(APIUtil.generateUUID())
|
||||
.PartialFunctionName(entity.partialFunctionName)
|
||||
.RequestVerb(entity.requestVerb)
|
||||
@ -59,13 +88,14 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
|
||||
}.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
|
||||
|
||||
|
||||
override def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc] = {
|
||||
override def update(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc] = {
|
||||
DynamicResourceDoc.find(By(DynamicResourceDoc.DynamicResourceDocId, entity.dynamicResourceDocId.getOrElse(""))) match {
|
||||
case Full(v) =>
|
||||
tryo {
|
||||
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
|
||||
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
|
||||
v.PartialFunctionName(entity.partialFunctionName)
|
||||
.BankId(bankId.getOrElse(null))
|
||||
.RequestVerb(entity.requestVerb)
|
||||
.RequestUrl(entity.requestUrl)
|
||||
.Summary(entity.summary)
|
||||
@ -82,8 +112,15 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
|
||||
}
|
||||
}
|
||||
|
||||
override def deleteById(id: String): Box[Boolean] = tryo {
|
||||
DynamicResourceDoc.bulkDelete_!!(By(DynamicResourceDoc.DynamicResourceDocId, id))
|
||||
override def deleteById(bankId: Option[String], id: String): Box[Boolean] = tryo {
|
||||
if(bankId.isEmpty) {
|
||||
DynamicResourceDoc.bulkDelete_!!(By(DynamicResourceDoc.DynamicResourceDocId, id))
|
||||
}else{
|
||||
DynamicResourceDoc.bulkDelete_!!(
|
||||
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
|
||||
By(DynamicResourceDoc.DynamicResourceDocId, id)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
68
obp-api/src/test/scala/code/util/DynamicEndpointsTest.scala
Normal file
68
obp-api/src/test/scala/code/util/DynamicEndpointsTest.scala
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2022, TESOBE GmbH.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH.
|
||||
Osloer Strasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
|
||||
*/
|
||||
|
||||
package code.util
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.v4_0_0.V400ServerSetup
|
||||
import code.api.v4_0_0.dynamic.CompiledObjects
|
||||
import code.setup.PropsReset
|
||||
import org.scalatest.Tag
|
||||
|
||||
|
||||
|
||||
class DynamicEndpointsTest extends V400ServerSetup with PropsReset {
|
||||
object DynamicUtilsTag extends Tag("DynamicEndpoints")
|
||||
|
||||
feature("test DynamicEndpoints.CompiledObjects.validateDependency method") {
|
||||
scenario("validateDependency should work well "){
|
||||
//This mean, we are only disabled the v4.0.0, all other versions should be enabled
|
||||
setPropsValues(
|
||||
"dynamic_code_compile_validate_enable" -> "true",
|
||||
"dynamic_code_compile_validate_dependencies" -> """[
|
||||
| NewStyle.function.getClass.getTypeName -> "*",
|
||||
| CompiledObjects.getClass.getTypeName -> "sandbox",
|
||||
| HttpCode.getClass.getTypeName -> "200",
|
||||
| DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",
|
||||
| APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",
|
||||
| ErrorMessages.getClass.getTypeName -> "*",
|
||||
| ExecutionContext.Implicits.getClass.getTypeName -> "global",
|
||||
| JSONFactory400.getClass.getTypeName -> "createBanksJson",
|
||||
| classOf[Sandbox].getTypeName -> "runInSandbox",
|
||||
| classOf[CallContext].getTypeName -> "*",
|
||||
| classOf[ResourceDoc].getTypeName -> "getPathParams",
|
||||
| "scala.reflect.runtime.package$" -> "universe",
|
||||
| PractiseEndpoint.getClass.getTypeName + "*" -> "*"
|
||||
|]""".stripMargin
|
||||
)
|
||||
|
||||
val jsonDynamicResourceDoc = SwaggerDefinitionsJSON.jsonDynamicResourceDoc
|
||||
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
|
||||
.validateDependency()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,13 +27,20 @@ TESOBE (http://www.tesobe.com/)
|
||||
|
||||
package code.util
|
||||
|
||||
import code.api.util.DynamicUtil.Sandbox
|
||||
import code.api.util._
|
||||
import code.setup.PropsReset
|
||||
import com.openbankproject.commons.model.BankId
|
||||
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.common.{Box}
|
||||
import net.liftweb.json
|
||||
import org.scalatest.{FeatureSpec, FlatSpec, GivenWhenThen, Matchers, Tag}
|
||||
|
||||
import java.io.File
|
||||
import java.security.{AccessControlException}
|
||||
import scala.collection.immutable.List
|
||||
import scala.io.Source
|
||||
|
||||
class DynamicUtilTest extends FlatSpec with Matchers {
|
||||
object DynamicUtilsTag extends Tag("DynamicUtil")
|
||||
|
||||
@ -48,6 +55,118 @@ class DynamicUtilTest extends FlatSpec with Matchers {
|
||||
getBankResponse should be (125)
|
||||
}
|
||||
|
||||
"DynamicUtil.compileScalaCode method" should "compile the permissions and dependences" taggedAs DynamicUtilsTag in {
|
||||
|
||||
val permissions:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode("""
|
||||
|List[java.security.Permission](
|
||||
| new java.net.NetPermission("specifyStreamHandler"),
|
||||
| new java.lang.reflect.ReflectPermission("suppressAccessChecks"),
|
||||
| new java.lang.RuntimePermission("getenv.*")
|
||||
| ) """.stripMargin)
|
||||
|
||||
val permissionList = permissions.openOrThrowException("Can not compile the string to permissions")
|
||||
Sandbox.createSandbox(permissionList)
|
||||
permissionList.toString contains ("""java.net.NetPermission""") shouldBe (true)
|
||||
|
||||
val permissionString =
|
||||
"""[new java.net.NetPermission("specifyStreamHandler"),
|
||||
|new java.lang.reflect.ReflectPermission("suppressAccessChecks"),
|
||||
|new java.lang.RuntimePermission("getenv.*"),
|
||||
|new java.util.PropertyPermission("cglib.useCache", "read"),
|
||||
|new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"),
|
||||
|new java.util.PropertyPermission("cglib.debugLocation", "read"),
|
||||
|new java.lang.RuntimePermission("accessDeclaredMembers"),
|
||||
|new java.lang.RuntimePermission("getClassLoader")]""".stripMargin
|
||||
|
||||
val scalaCode = "List[java.security.Permission]"+permissionString.replaceFirst("\\[","(").dropRight(1)+")"
|
||||
val permissions2:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode(scalaCode)
|
||||
|
||||
val permissionList2 = permissions2.openOrThrowException("Can not compile the string to permissions")
|
||||
Sandbox.createSandbox(permissionList2)
|
||||
permissionList2.toString contains ("""java.net.NetPermission""") shouldBe (true)
|
||||
|
||||
|
||||
val dependenciesBox: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(s"${DynamicUtil.importStatements}"+"""
|
||||
|
|
||||
|Map(
|
||||
| // companion objects methods
|
||||
| NewStyle.function.getClass.getTypeName -> "*",
|
||||
| CompiledObjects.getClass.getTypeName -> "sandbox",
|
||||
| HttpCode.getClass.getTypeName -> "200",
|
||||
| DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",
|
||||
| APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",
|
||||
| ErrorMessages.getClass.getTypeName -> "*",
|
||||
| ExecutionContext.Implicits.getClass.getTypeName -> "global",
|
||||
| JSONFactory400.getClass.getTypeName -> "createBanksJson",
|
||||
|
|
||||
| // class methods
|
||||
| classOf[Sandbox].getTypeName -> "runInSandbox",
|
||||
| classOf[CallContext].getTypeName -> "*",
|
||||
| classOf[ResourceDoc].getTypeName -> "getPathParams",
|
||||
| "scala.reflect.runtime.package$" -> "universe",
|
||||
|
|
||||
| // allow any method of PractiseEndpoint for test
|
||||
| PractiseEndpoint.getClass.getTypeName + "*" -> "*",
|
||||
|
|
||||
| ).mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)""".stripMargin)
|
||||
val dependencies = dependenciesBox.openOrThrowException("Can not compile the string to Map")
|
||||
dependencies.toString contains ("code.api.util.NewStyle") shouldBe (true)
|
||||
|
||||
val dependenciesString = """[NewStyle.function.getClass.getTypeName -> "*",CompiledObjects.getClass.getTypeName -> "sandbox",HttpCode.getClass.getTypeName -> "200",DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",ErrorMessages.getClass.getTypeName -> "*",ExecutionContext.Implicits.getClass.getTypeName -> "global",JSONFactory400.getClass.getTypeName -> "createBanksJson",classOf[Sandbox].getTypeName -> "runInSandbox",classOf[CallContext].getTypeName -> "*",classOf[ResourceDoc].getTypeName -> "getPathParams","scala.reflect.runtime.package$" -> "universe",PractiseEndpoint.getClass.getTypeName + "*" -> "*"]""".stripMargin
|
||||
|
||||
val scalaCode2 = s"${DynamicUtil.importStatements}"+dependenciesString.replaceFirst("\\[","Map(").dropRight(1) +").mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)"
|
||||
val dependenciesBox2: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(scalaCode2)
|
||||
val dependencies2 = dependenciesBox2.openOrThrowException("Can not compile the string to Map")
|
||||
dependencies2.toString contains ("code.api.util.NewStyle") shouldBe (true)
|
||||
}
|
||||
|
||||
"Sandbox.createSandbox method" should "should throw exception" taggedAs DynamicUtilsTag in {
|
||||
val permissionList = List(
|
||||
// new java.net.SocketPermission("ir.dcs.gla.ac.uk:80","connect,resolve"),
|
||||
)
|
||||
|
||||
intercept[AccessControlException] {
|
||||
Sandbox.createSandbox(permissionList).runInSandbox {
|
||||
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Sandbox.createSandbox method" should "should work well" taggedAs DynamicUtilsTag in {
|
||||
val permissionList = List(
|
||||
new java.net.SocketPermission("apisandbox.openbankproject.com:443","connect,resolve"),
|
||||
new java.util.PropertyPermission("user.dir","read"),
|
||||
new java.io.FilePermission("README.md","read"),
|
||||
)
|
||||
|
||||
Sandbox.createSandbox(permissionList).runInSandbox {
|
||||
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
|
||||
Source.fromFile("README.md").getLines
|
||||
new File(".").getCanonicalPath
|
||||
}
|
||||
}
|
||||
|
||||
"Sandbox.sandbox method test bankId" should "should throw exception" taggedAs DynamicUtilsTag in {
|
||||
intercept[AccessControlException] {
|
||||
Sandbox.sandbox(bankId= "abc").runInSandbox {
|
||||
BankId("123" )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Sandbox.sandbox method test bankId" should "should work well" taggedAs DynamicUtilsTag in {
|
||||
Sandbox.sandbox(bankId= "abc").runInSandbox {
|
||||
BankId("abc" )
|
||||
}
|
||||
}
|
||||
|
||||
"Sandbox.sandbox method test default permission" should "should throw exception" taggedAs DynamicUtilsTag in {
|
||||
// intercept[AccessControlException] {
|
||||
Sandbox.sandbox(bankId= "abc").runInSandbox {
|
||||
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
val zson = {
|
||||
"""
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<groupId>com.tesobe</groupId>
|
||||
<artifactId>obp-parent</artifactId>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<version>1.10.0</version>
|
||||
<version>1.10.1</version>
|
||||
</parent>
|
||||
<artifactId>obp-commons</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -2,9 +2,12 @@ package com.openbankproject.commons
|
||||
|
||||
import com.alibaba.ttl.TtlRunnable
|
||||
|
||||
import scala.concurrent.{ ExecutionContext => ScalaExecutionContext}
|
||||
import java.security.{AccessController, PrivilegedAction}
|
||||
import scala.concurrent.{ExecutionContext => ScalaExecutionContext}
|
||||
|
||||
object ExecutionContext {
|
||||
val enableSandbox = System.getProperty("dynamic_code_sandbox_enable", "false").toBoolean
|
||||
|
||||
object Implicits {
|
||||
/**
|
||||
* The implicit global `ExecutionContext`. Import `global` when you want to provide the global
|
||||
@ -24,8 +27,17 @@ object ExecutionContext {
|
||||
*/
|
||||
def wrapExecutionContext(executionContext: ScalaExecutionContext): ScalaExecutionContext = {
|
||||
new ScalaExecutionContext{
|
||||
override def execute(runnable: Runnable): Unit = executionContext.execute(TtlRunnable.get(runnable, true, true))
|
||||
override def execute(runnable: Runnable): Unit = {
|
||||
val privilegedRunnable = if(enableSandbox) PrivilegedRunnable(runnable) else runnable
|
||||
executionContext.execute(TtlRunnable.get(privilegedRunnable, true, true))
|
||||
}
|
||||
override def reportFailure(cause: Throwable): Unit = executionContext.reportFailure(cause)
|
||||
}
|
||||
}
|
||||
|
||||
def PrivilegedRunnable(runnable: Runnable): Runnable = {
|
||||
val acc = AccessController.getContext
|
||||
val privilegedAction: PrivilegedAction[Unit] = () => runnable.run()
|
||||
() => AccessController.doPrivileged(privilegedAction, acc)
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,9 +27,10 @@ TESOBE (http://www.tesobe.com/)
|
||||
package com.openbankproject.commons.model
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import com.openbankproject.commons.util.{OBPRequired, optional}
|
||||
|
||||
import java.security.AccessControlContext
|
||||
import javax.security.auth.AuthPermission
|
||||
import scala.collection.immutable.List
|
||||
import scala.math.BigDecimal
|
||||
|
||||
@ -138,11 +139,25 @@ object AccountId {
|
||||
}
|
||||
|
||||
case class BankId(value : String) {
|
||||
BankId.checkPermission(value)
|
||||
|
||||
override def toString = value
|
||||
}
|
||||
|
||||
object BankId {
|
||||
def unapply(id : String) = Some(BankId(id))
|
||||
|
||||
def checkPermission(bankId: String): Unit = {
|
||||
val sm = System.getSecurityManager
|
||||
// use AuthPermission to control bankId value, it is a hack way.
|
||||
if (sm != null) { //only check the permission when we using the security manager.
|
||||
val securityContext = sm.getSecurityContext.asInstanceOf[AccessControlContext]
|
||||
sm.checkPermission(permission(bankId), securityContext)
|
||||
}
|
||||
}
|
||||
def checkPermission(bankId: Option[String]): Unit = bankId.foreach(checkPermission)
|
||||
|
||||
def permission(bankId: String) = new AuthPermission(s"You do not have the permission for the BANK_ID($bankId)")
|
||||
}
|
||||
|
||||
case class AccountRoutingAddress(val value: String) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.openbankproject.commons.util
|
||||
|
||||
import java.lang.reflect.{Constructor, Modifier, Parameter}
|
||||
|
||||
import com.openbankproject.commons.model.{JsonFieldReName, ListResult}
|
||||
import com.openbankproject.commons.model.enums.{SimpleEnum, SimpleEnumCollection}
|
||||
import com.openbankproject.commons.util.Functions.Implicits._
|
||||
@ -20,6 +19,24 @@ import scala.reflect.runtime.{universe => ru}
|
||||
|
||||
object JsonSerializers {
|
||||
|
||||
object CustomFormats extends DefaultFormats {
|
||||
private val defaultFormats = net.liftweb.json.DefaultFormats
|
||||
val losslessDate = defaultFormats.losslessDate
|
||||
val UTC = defaultFormats.UTC
|
||||
|
||||
/**
|
||||
* DefaultFormats#parameterNameReader has bug, when execute fail, cause return Nil, this is not reasonable,
|
||||
* Here override it to: when execute fail, try to call constructor.getParamters
|
||||
*/
|
||||
override val parameterNameReader: ParameterNameReader = new ParameterNameReader {
|
||||
override def lookupParameterNames(constructor: Constructor[_]): Traversable[String] = try {
|
||||
defaultFormats.parameterNameReader.lookupParameterNames(constructor)
|
||||
} catch {
|
||||
case _ => constructor.getParameters.map(_.getName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val serializers: List[Serializer[_]] =
|
||||
AbstractTypeDeserializer :: SimpleEnumDeserializer ::
|
||||
BigDecimalSerializer :: StringDeserializer ::
|
||||
@ -27,7 +44,7 @@ object JsonSerializers {
|
||||
JsonAbleSerializer :: ListResultSerializer.asInstanceOf[Serializer[_]] :: // here must do class cast, or it cause compile error, looks like a bug of scala.
|
||||
MapperSerializer :: Nil
|
||||
|
||||
implicit val commonFormats = net.liftweb.json.DefaultFormats ++ serializers
|
||||
implicit val commonFormats = CustomFormats ++ serializers
|
||||
|
||||
val nullTolerateFormats = commonFormats + JNothingSerializer
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user