Skip to content

Commit

Permalink
Implement JSON-RPC via REST-Api endpoint '/jsonrpc'
Browse files Browse the repository at this point in the history
For details and examples see Readme.md
  • Loading branch information
sfeilmeier committed Feb 25, 2019
1 parent 55436e7 commit ae53400
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 12 deletions.
45 changes: 41 additions & 4 deletions io.openems.edge.controller.api.rest/readme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
# io.openems.edge.controller.api.rest Provider
# REST-Api Controller

${Bundle-Description}
## Endpoint '/rest/channel/{Component-ID}/{Channel-ID}'

## Example
### GET

## References
### POST

```
{
"value": 1000
}
```

## Endpoint '/jsonrpc'

Properties 'id' and 'jsonrpc' can be omitted, as they are not required for HTTP POST calls.

### getEdgeConfig

```
{
"method": "getEdgeConfig",
"params": {}
}
```

### componentJsonApi

#### getModbusProtocol

```
{
"method":"componentJsonApi",
"params":{
"componentId":"ctrlApiModbusTcp0",
"payload":{
"method":"getModbusProtocol",
"params":{
}
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import javax.servlet.ServletException;
Expand All @@ -21,12 +24,23 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.openems.common.OpenemsConstants;
import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess;
import io.openems.common.jsonrpc.base.JsonrpcMessage;
import io.openems.common.jsonrpc.base.JsonrpcRequest;
import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
import io.openems.common.jsonrpc.request.ComponentJsonApiRequest;
import io.openems.common.jsonrpc.request.GetEdgeConfigRequest;
import io.openems.common.types.ChannelAddress;
import io.openems.common.types.OpenemsType;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.WriteChannel;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.jsonapi.JsonApi;
import io.openems.edge.controller.api.core.WritePojo;

public class RestHandler extends AbstractHandler {
Expand Down Expand Up @@ -58,6 +72,10 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
case "rest":
this.handleRest(remainingTargets, baseRequest, request, response);
break;

case "jsonrpc":
this.handleJsonRpc(baseRequest, request, response);
break;
}
} catch (OpenemsNamedException e) {
throw new IOException(e.getMessage());
Expand Down Expand Up @@ -180,14 +198,7 @@ private void handlePost(Channel<?> channel, Request baseRequest, HttpServletRequ
}

// parse json
JsonParser parser = new JsonParser();
JsonObject jHttpPost;
try {
jHttpPost = parser.parse(new BufferedReader(new InputStreamReader(baseRequest.getInputStream())).lines()
.collect(Collectors.joining("\n"))).getAsJsonObject();
} catch (Exception e) {
throw new OpenemsException("Unable to parse: " + e.getMessage());
}
JsonObject jHttpPost = RestHandler.parseJson(baseRequest);

// parse value
JsonElement jValue;
Expand All @@ -209,4 +220,162 @@ private void handlePost(Channel<?> channel, Request baseRequest, HttpServletRequ

this.sendOkResponse(baseRequest, response, new JsonObject());
}

/**
* Parses a Request to JSON.
*
* @param baseRequest the Request
* @return
* @throws OpenemsException on error
*/
private static JsonObject parseJson(Request baseRequest) throws OpenemsException {
JsonParser parser = new JsonParser();
try {
return parser.parse(new BufferedReader(new InputStreamReader(baseRequest.getInputStream())).lines()
.collect(Collectors.joining("\n"))).getAsJsonObject();
} catch (Exception e) {
throw new OpenemsException("Unable to parse: " + e.getMessage());
}
}

/**
* Handles an http request to 'jsonrpc' endpoint.
*
* @param edgeRpcRequest the EdgeRpcRequest
* @return the JSON-RPC Success Response Future
* @throws OpenemsNamedException on error
* @throws ExecutionException
* @throws InterruptedException
*/
private void handleJsonRpc(Request baseRequest, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
throws OpenemsNamedException {
// call handler methods
if (!httpRequest.getMethod().equals("POST")) {
throw new OpenemsException(
"Method [" + httpRequest.getMethod() + "] is not supported for JSON-RPC endpoint");
}

// parse json and add "jsonrpc" and "id" properties if missing
JsonObject json = RestHandler.parseJson(baseRequest);
if (!json.has("jsonrpc")) {
json.addProperty("jsonrpc", "2.0");
}
if (!json.has("id")) {
json.addProperty("id", UUID.randomUUID().toString());
}
if (json.has("params")) {
JsonObject params = JsonUtils.getAsJsonObject(json, "params");
if (params.has("payload")) {
JsonObject payload = JsonUtils.getAsJsonObject(params, "payload");
if (!payload.has("jsonrpc")) {
payload.addProperty("jsonrpc", "2.0");
}
if (!payload.has("id")) {
payload.addProperty("id", UUID.randomUUID().toString());
}
params.add("payload", payload);
}
json.add("params", params);
}

// parse JSON-RPC Request
JsonrpcMessage message = JsonrpcMessage.from(json);
if (!(message instanceof JsonrpcRequest)) {
throw new OpenemsException("Only JSON-RPC Request is supported here.");
}
JsonrpcRequest request = (JsonrpcRequest) message;

// handle the request
CompletableFuture<JsonrpcResponseSuccess> responseFuture = this.handleJsonRpcRequest(request);

// wait for response
JsonrpcResponseSuccess response;
try {
response = responseFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new OpenemsException("Unable to get Response: " + e.getMessage());
}

// send response
this.sendOkResponse(baseRequest, httpResponse, response.toJsonObject());
}

/**
* Handles an JSON-RPC Request.
*
* @param edgeRpcRequest the EdgeRpcRequest
* @return the JSON-RPC Success Response Future
* @throws OpenemsException on error
*/
private CompletableFuture<JsonrpcResponseSuccess> handleJsonRpcRequest(JsonrpcRequest request)
throws OpenemsException, OpenemsNamedException {
switch (request.getMethod()) {

case GetEdgeConfigRequest.METHOD:
return this.handleGetEdgeConfigRequest(GetEdgeConfigRequest.from(request));

case ComponentJsonApiRequest.METHOD:
return this.handleComponentJsonApiRequest(ComponentJsonApiRequest.from(request));

default:
this.parent.logWarn(this.log, "Unhandled Request: " + request);
throw OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getMethod());
}
}

/**
* Handles a GetEdgeConfigRequest.
*
* @param getEdgeConfigRequest the GetEdgeConfigRequest
* @return the JSON-RPC Success Response Future
* @throws OpenemsNamedException on error
*/
private CompletableFuture<JsonrpcResponseSuccess> handleGetEdgeConfigRequest(
GetEdgeConfigRequest getEdgeConfigRequest) throws OpenemsNamedException {
// wrap original request inside ComponentJsonApiRequest
ComponentJsonApiRequest request = new ComponentJsonApiRequest(OpenemsConstants.COMPONENT_MANAGER_ID,
getEdgeConfigRequest);

return this.handleComponentJsonApiRequest(request);
}

/**
* Handles a ComponentJsonApiRequest.
*
* @param request the ComponentJsonApiRequest
* @return the JSON-RPC Success Response Future
* @throws OpenemsNamedException on error
*/
private CompletableFuture<JsonrpcResponseSuccess> handleComponentJsonApiRequest(ComponentJsonApiRequest request)
throws OpenemsNamedException {
// get Component
String componentId = request.getComponentId();
OpenemsComponent component = this.parent.componentManager.getComponent(componentId);

if (component == null) {
throw new OpenemsException("Unable to find Component [" + componentId + "]");
}

if (!(component instanceof JsonApi)) {
throw new OpenemsException("Component [" + componentId + "] is no JsonApi");
}

// call JsonApi
JsonApi jsonApi = (JsonApi) component;
CompletableFuture<JsonrpcResponseSuccess> responseFuture = jsonApi.handleJsonrpcRequest(request.getPayload());

// handle null response
if (responseFuture == null) {
OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getPayload().getMethod());
}

// Wrap reply in EdgeRpcResponse
CompletableFuture<JsonrpcResponseSuccess> edgeRpcResponse = new CompletableFuture<>();
responseFuture.thenAccept(response -> {
edgeRpcResponse.complete(new GenericJsonrpcResponseSuccess(request.getId(), response.getResult()));
});

return edgeRpcResponse;
}

}

0 comments on commit ae53400

Please sign in to comment.