Skip to content

Commit

Permalink
#862 Added Viewport scoped rpc service to test.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjstevo committed Nov 5, 2023
1 parent 219b7f3 commit e39dc90
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 119 deletions.
11 changes: 11 additions & 0 deletions docs/rpc/Viewport_rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SvgDottySeparator } from "@site/src/components/SvgDottySeparator";

# RPC Calls

<SvgDottySeparator style={{marginBottom: 32}}/>

## Viewport RPC

```scala
In Progress
```
31 changes: 22 additions & 9 deletions docs/rpc/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import { SvgDottySeparator } from "@site/src/components/SvgDottySeparator";

<SvgDottySeparator style={{marginBottom: 32}}/>

## Menus
## Overview of RPC

[Menu Items](Menu_items.md) act upon a `table`, `selection`, `row` or `cell` (these are called `scope`).
There are two scopes where rpc services can be defined:

Once a `menu item` is registered by a server side [`provider`](../providers_tables_viewports/providers.md), it will be automatically displayed when user right-clicks on the corresponding Vuu Grid component.
- Global Scope - these are services that can be called without a viewport being created.
- Viewport Scope - these are services that are created when a user creates a viewport

Menu items may have filter expressions (applied for each individual row) that determine for which rows they are visible. If a menu item is visible, it can be invoked. On invocation, depending on the `scope` the RPC handler will receive context information about what are we acting upon.
## Global Scope - RPC Services

[RPC Services](service.md) allow us to expose server-side functionality to a Vuu client over a low-latency web-socket connection.

The Vuu client framework can discover and programmatically call these services over the WebSocket. While there is no generic UI for invoking/inspecting REST services, many components (such as the Autocomplete Search) use services as an implementation mechanism.

## Global Scope - REST Services

## RPC Services
[REST Services]() allow us to expose server-side functionality to a Vuu client. Each service is modeled in REST-ful resource fashion, and can define the following standard verbs: `get_all`, `get`, `post`, `put`, `delete`

[RPC Services](service.md) allow us to expose server-side functionality to a Vuu client over a low-latency connection.

The Vuu client framework can discover and programmatically call these services over the WebSocket. While there is no generic UI for invoking/inspecting REST services, many components (such as the Autocomplete Search) use REST services as an implementation mechanism.
## Viewport Scope - Menu Items
[Menu Items](Menu_items.md) act upon a `table`, `selection`, `row` or `cell` (these are called `scope`).

Once a `menu item` is registered by a server side [`provider`](../providers_tables_viewports/providers.md), it will be automatically displayed when user right-clicks on the corresponding Vuu Grid component.

Menu items may have filter expressions (applied for each individual row) that determine for which rows they are visible. If a menu item is visible, it can be invoked. On invocation, depending on the `scope` the RPC handler will receive context information about what are we acting upon.

## REST Services
## Viewport Scope - RPC Calls
[Viewport RPC](Viewport_rpc.md) calls are specific service methods that we want to call on a viewport we've created. They are specific i.e. the UI component needs
to understand the type of call that is being called. In that way they should be used in functionally specific UI components.

[REST Services](#) allow us to expose server-side functionality to a Vuu client. Each service is modeled in REST-ful resource fashion, and can define the following standard verbs: `get_all`, `get`, `post`, `put`, `delete`
They implicitly have access to the viewport and its associated tables that they are being called on.
11 changes: 11 additions & 0 deletions vuu/src/main/scala/org/finos/vuu/core/CoreServerApiHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ class CoreServerApiHandler(val viewPortContainer: ViewPortContainer,
val providers: ProviderContainer)(implicit timeProvider: Clock) extends ServerApi with StrictLogging {


override def process(msg: ViewPortRpcCall)(ctx: RequestContext): Option[ViewServerMessage] = {
Try(viewPortContainer.callRpcService(msg.vpId, msg.rpcName, msg.params, msg.namedParams, ctx.session)(ctx)) match {
case Success(action) =>
logger.info("Processed VP RPC call" + msg)
vsMsg(ViewPortMenuRpcResponse(msg.vpId, msg.rpcName, action))(ctx)
case Failure(e) =>
logger.info("Failed to remove viewport", e)
vsMsg(ViewPortMenuRpcReject(msg.vpId, msg.rpcName, e.getMessage))(ctx)
}
}

override def process(msg: ViewPortMenuCellRpcCall)(ctx: RequestContext): Option[ViewServerMessage] = {
Try(viewPortContainer.callRpcCell(msg.vpId, msg.rpcName, ctx.session, msg.rowKey, msg.field, msg.value)) match {
case Success(action) =>
Expand Down
3 changes: 2 additions & 1 deletion vuu/src/main/scala/org/finos/vuu/net/Messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ case class ChangeViewPortRangeSuccess(viewPortId: String, from: Int, to: Int) ex

case class OpenTreeNodeRequest(vpId: String, treeKey: String) extends MessageBody

case class ViewPortMenuRpcCall()
case class ViewPortRpcCall(vpId: String, rpcName: String, params: Array[Any], namedParams: Map[String, Any]) extends MessageBody
//case class RpcCall(service: String, method: String, params: Array[Any], namedParams: Map[String, Any]) extends MessageBody

case class ViewPortMenuSelectionRpcCall(vpId: String, rpcName: String) extends MessageBody

Expand Down
2 changes: 1 addition & 1 deletion vuu/src/main/scala/org/finos/vuu/net/ServerApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ trait ServerApi {
def process(msg: ViewPortMenuTableRpcCall)(ctx: RequestContext): Option[ViewServerMessage]

def process(msg: ViewPortMenuSelectionRpcCall)(ctx: RequestContext): Option[ViewServerMessage]

def process(msg: ViewPortRpcCall)(ctx: RequestContext): Option[ViewServerMessage]
def process(msg: ViewPortEditCellRpcCall)(ctx: RequestContext): Option[ViewServerMessage]

def process(msg: ViewPortEditRowRpcCall)(ctx: RequestContext): Option[ViewServerMessage]
Expand Down
96 changes: 78 additions & 18 deletions vuu/src/main/scala/org/finos/vuu/net/rpc/RpcHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import com.typesafe.scalalogging.StrictLogging
import org.finos.vuu.net._
import org.finos.vuu.viewport._

import java.lang.reflect.Method
import java.lang.reflect.{Method, Type}

trait RpcHandler extends StrictLogging {

def menuItems(): ViewPortMenu = EmptyViewPortMenu

def menusAsMap() = {
def menusAsMap(): Map[String, ViewPortMenuItem] = {

val menus = menuItems()

Expand All @@ -32,48 +32,87 @@ trait RpcHandler extends StrictLogging {
foldMenus(menus)(Map())
}

lazy val menuMap = menusAsMap()
lazy val menuMap: Map[String, ViewPortMenuItem] = menusAsMap()

def implementsService(serviceIf: String): Boolean = {
this.getClass.getInterfaces.exists(_.getSimpleName == serviceIf)
}

val methodsAndParams = this.getClass.getMethods.map(method => (method.getName, method.getGenericParameterTypes, method)).groupBy(_._1).toMap
val methodsAndParams: Map[String, Array[(String, Array[Type], Method)]] = this.getClass.getMethods.map(method => (method.getName, method.getGenericParameterTypes, method)).groupBy(_._1)

def processViewPortRpcCall(methodName: String, params: Array[Any], namedParams: Map[String, Any])(ctx: RequestContext):ViewPortAction = {

if (!methodsAndParams.contains(methodName)) {
ViewPortRpcFailure(s"Could not find method $methodName")
} else {

val overloadedMethods = methodsAndParams(methodName)

val method = findBestMatchingMethod(methodName, params, overloadedMethods)

try {
val r = if (params.length == 0)
method.get.invoke(this, ctx)
else if (params.length == 1)
method.get.invoke(this, toO(params(0)), ctx)
else if (params.length == 2)
method.get.invoke(this, toO(params(0)), toO(params(1)), ctx)
else if (params.length == 3)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), ctx)
else if (params.length == 4)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), toO(params(3)), ctx)
else if (params.length == 5)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), toO(params(3)), toO(params(4)), ctx)
else if (params.length == 6)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), toO(params(3)), toO(params(4)), toO(params(5)), ctx)
else if (params.length == 7)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), toO(params(3)), toO(params(4)), toO(params(5)), toO(params(6)), ctx)
else if (params.length == 8)
method.get.invoke(this, toO(params(0)), toO(params(1)), toO(params(2)), toO(params(3)), toO(params(4)), toO(params(5)), toO(params(6)), toO(params(7)), ctx)

r.asInstanceOf[ViewPortAction]
} catch {
case ex: Exception =>
logger.error(s"Exception occurred calling rpc $method", ex)
ViewPortRpcFailure(s"Exception occured calling rpc $method")
}
}
}

def processRpcCall(msg: ViewServerMessage, rpc: RpcCall)(ctx: RequestContext): Option[ViewServerMessage] = {

if (!methodsAndParams.contains(rpc.method)) {
onError(s"error could not find method ${rpc.method}", 1)
} else {

val overloadedMethods = methodsAndParams.get(rpc.method).get
val overloadedMethods = methodsAndParams(rpc.method)

val method = findBestMatchingMethod(rpc, overloadedMethods)

try{
val r = if (rpc.params.size == 0)
val r = if (rpc.params.length == 0)
method.get.invoke(this, ctx)
else if (rpc.params.size == 1)
else if (rpc.params.length == 1)
method.get.invoke(this, toO(rpc.params(0)), ctx)
else if (rpc.params.size == 2)
else if (rpc.params.length == 2)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), ctx)
else if (rpc.params.size == 3)
else if (rpc.params.length == 3)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), ctx)
else if (rpc.params.size == 4)
else if (rpc.params.length == 4)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), toO(rpc.params(3)), ctx)
else if (rpc.params.size == 5)
else if (rpc.params.length == 5)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), toO(rpc.params(3)), toO(rpc.params(4)), ctx)
else if (rpc.params.size == 6)
else if (rpc.params.length == 6)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), toO(rpc.params(3)), toO(rpc.params(4)), toO(rpc.params(5)), ctx)
else if (rpc.params.size == 7)
else if (rpc.params.length == 7)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), toO(rpc.params(3)), toO(rpc.params(4)), toO(rpc.params(5)), toO(rpc.params(6)), ctx)
else if (rpc.params.size == 8)
else if (rpc.params.length == 8)
method.get.invoke(this, toO(rpc.params(0)), toO(rpc.params(1)), toO(rpc.params(2)), toO(rpc.params(3)), toO(rpc.params(4)), toO(rpc.params(5)), toO(rpc.params(6)), toO(rpc.params(7)), ctx)

Some(VsMsg(ctx.requestId, ctx.session.sessionId, ctx.token, ctx.session.user, RpcResponse(rpc.method, r, null), module = msg.module))
}catch{
case ex: Exception =>
logger.error(s"Exception occurred calling rpc ${rpc}", ex)
logger.error(s"Exception occurred calling rpc $rpc", ex)
Some(VsMsg(ctx.requestId, ctx.session.sessionId, ctx.token, ctx.session.user, RpcResponse(rpc.method, null, Error(ex.getMessage, ex.hashCode())), module = msg.module))
}

Expand All @@ -85,23 +124,44 @@ trait RpcHandler extends StrictLogging {

def findBestMatchingMethod(rpc: RpcCall, methods: Array[(String, Array[java.lang.reflect.Type], Method)]): Option[Method] = {

val size = rpc.params.size + 1
val size = rpc.params.length + 1

val filteredBySize = methods.filter(_._2.size == size)
val filteredBySize = methods.filter(_._2.length == size)

filteredBySize.find(m => paramsEqualsRpcParams(m._3, rpc)) match {
case Some(tuple) => Some(tuple._3)
case None => None
}
}

def findBestMatchingMethod(method: String, params: Array[Any], methods: Array[(String, Array[java.lang.reflect.Type], Method)]): Option[Method] = {

val size = params.length + 1

val filteredBySize = methods.filter(_._2.length == size)

filteredBySize.find(m => paramsEqualsRpcParams(m._3, params)) match {
case Some(tuple) => Some(tuple._3)
case None => None
}
}

def paramsEqualsRpcParams(method: Method, params: Array[Any]): Boolean = {

val assignable = params.zip(method.getGenericParameterTypes).map({ case (value, classParam) =>
classParam.getClass.isAssignableFrom(value.getClass)
})

assignable.length == params.length
}

def paramsEqualsRpcParams(method: Method, rpcCall: RpcCall): Boolean = {

val assignable = rpcCall.params.zip(method.getGenericParameterTypes).map({ case (value, classParam) =>
classParam.getClass.isAssignableFrom(value.getClass)
})

assignable.size == rpcCall.params.size
assignable.length == rpcCall.params.length
}

def onError(message: String, code: Int): Option[ViewServerMessage] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import org.finos.vuu.core.sort._
import org.finos.vuu.core.table.{DataTable, SessionTable, TableContainer}
import org.finos.vuu.core.tree.TreeSessionTableImpl
import org.finos.vuu.net.rpc.EditRpcHandler
import org.finos.vuu.net.{ClientSessionId, FilterSpec, SortSpec}
import org.finos.vuu.net.{ClientSessionId, FilterSpec, RequestContext, SortSpec}
import org.finos.vuu.provider.{Provider, ProviderContainer}
import org.finos.vuu.util.PublishQueue
import org.finos.vuu.viewport.tree._
Expand Down Expand Up @@ -45,10 +45,6 @@ trait ViewPortContainerMBean {

}

object ViewPortContainerMetrics {

}

class ViewPortContainer(val tableContainer: TableContainer, val providerContainer: ProviderContainer)(implicit timeProvider: Clock, metrics: MetricsProvider) extends RunInThread with StrictLogging with JmxAble with ViewPortContainerMBean {

import org.finos.vuu.core.VuuServerMetrics._
Expand Down Expand Up @@ -78,6 +74,12 @@ class ViewPortContainer(val tableContainer: TableContainer, val providerContaine
treeNodeStatesByVp.get(vpId)
}

def callRpcService(vpId: String, method: String, params: Array[Any], namedParams: Map[String, Any], session: ClientSessionId)(ctx: RequestContext): ViewPortAction = {
val viewPort = this.getViewPortById(vpId)
val viewPortDef = viewPort.getStructure.viewPortDef
viewPortDef.service.processViewPortRpcCall(method, params, namedParams)(ctx)
}

def callRpcCell(vpId: String, rpcName: String, session: ClientSessionId, rowKey: String, field: String, singleValue: Object): ViewPortAction = {

val viewPort = this.getViewPortById(vpId)
Expand Down
2 changes: 2 additions & 0 deletions vuu/src/main/scala/org/finos/vuu/viewport/ViewPortEdit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import org.finos.vuu.net.ClientSessionId

trait ViewPortEditAction extends ViewPortAction {}
case class ViewPortEditSuccess() extends ViewPortEditAction {}
case class ViewPortRpcSuccess() extends ViewPortAction {}
case class ViewPortRpcFailure(msg: String) extends ViewPortAction {}
case class ViewPortEditFailure(msg: String) extends ViewPortEditAction {}

case class ViewPortEditCellAction(filter: String, func: (String, String, Object, ViewPort, ClientSessionId) => ViewPortEditAction){
Expand Down
Loading

0 comments on commit e39dc90

Please sign in to comment.