package de.rwth.idsg.steve.ocpp.ws.pipeline;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.rwth.idsg.steve.SteveException;
import de.rwth.idsg.steve.ocpp.RequestType;
import de.rwth.idsg.steve.ocpp.ResponseType;
import de.rwth.idsg.steve.ocpp.ws.ErrorFactory;
import de.rwth.idsg.steve.ocpp.ws.FutureResponseContextStore;
import de.rwth.idsg.steve.ocpp.ws.TypeStore;
import de.rwth.idsg.steve.ocpp.ws.data.CommunicationContext;
import de.rwth.idsg.steve.ocpp.ws.data.ErrorCode;
import de.rwth.idsg.steve.ocpp.ws.data.FutureResponseContext;
import de.rwth.idsg.steve.ocpp.ws.data.MessageType;
import de.rwth.idsg.steve.ocpp.ws.data.OcppJsonCall;
import de.rwth.idsg.steve.ocpp.ws.data.OcppJsonError;
import de.rwth.idsg.steve.ocpp.ws.data.OcppJsonResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
/**
* Incoming String --> OcppJsonMessage
*
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
* @since 17.03.2015
*/
@Slf4j
@RequiredArgsConstructor
public class Deserializer implements Stage {
private final ObjectMapper mapper;
private final FutureResponseContextStore futureResponseContextStore;
private final TypeStore typeStore;
/**
* Parsing with streaming API is cumbersome, but only it allows to parse the String step for step
* and build, if any, a corresponding error message.
*/
@Override
public void process(CommunicationContext context) {
try (JsonParser parser = mapper.getFactory().createParser(context.getIncomingString())) {
parser.nextToken(); // set cursor to '['
parser.nextToken();
int messageTypeNr = parser.getIntValue();
parser.nextToken();
String messageId = parser.getText();
MessageType messageType = MessageType.fromTypeNr(messageTypeNr);
switch (messageType) {
case CALL:
handleCall(context, messageId, parser);
break;
case CALL_RESULT:
handleResult(context, messageId, parser);
break;
case CALL_ERROR:
handleError(context, messageId, parser);
break;
default:
throw new SteveException("Unknown enum type");
}
} catch (IOException e) {
throw new SteveException("Deserialization of incoming string failed: %s", context.getIncomingString(), e);
}
}
// -------------------------------------------------------------------------
// Private Helpers
// -------------------------------------------------------------------------
/**
* Catch exceptions and wrap them in outgoing ERRORs for incoming CALLs.
*/
private void handleCall(CommunicationContext context, String messageId, JsonParser parser) {
// parse action
String action;
try {
parser.nextToken();
action = parser.getText();
} catch (IOException e) {
log.error("Exception occurred", e);
context.setOutgoingMessage(ErrorFactory.genericDeserializeError(messageId, e.getMessage()));
return;
}
// find action class
Class<? extends RequestType> clazz = typeStore.findRequestClass(action);
if (clazz == null) {
context.setOutgoingMessage(ErrorFactory.actionNotFound(messageId, action));
return;
}
// parse request payload
RequestType req;
try {
parser.nextToken();
JsonNode requestPayload = parser.readValueAsTree();
req = mapper.treeToValue(requestPayload, clazz);
} catch (IOException e) {
log.error("Exception occurred", e);
context.setOutgoingMessage(ErrorFactory.payloadDeserializeError(messageId, e.getMessage()));
return;
}
OcppJsonCall call = new OcppJsonCall();
call.setMessageId(messageId);
call.setAction(action);
call.setPayload(req);
context.setIncomingMessage(call);
}
/**
* Do NOT catch and handle exceptions for incoming RESPONSEs. Let the processing fail.
* There is no mechanism in OCPP to report back such erroneous messages.
*/
private void handleResult(CommunicationContext context, String messageId, JsonParser parser) {
FutureResponseContext responseContext = futureResponseContextStore.get(context.getSession(), messageId);
if (responseContext == null) {
throw new SteveException(
"A result message was received as response to a not-sent call. The message was: %s",
context.getIncomingString()
);
}
ResponseType res;
try {
parser.nextToken();
JsonNode responsePayload = parser.readValueAsTree();
res = mapper.treeToValue(responsePayload, responseContext.getResponseClass());
} catch (IOException e) {
throw new SteveException("Deserialization of incoming response payload failed", e);
}
OcppJsonResult result = new OcppJsonResult();
result.setMessageId(messageId);
result.setPayload(res);
context.setIncomingMessage(result);
context.setHandler(responseContext.getHandler());
}
/**
* Do NOT catch and handle exceptions for incoming RESPONSEs. Let the processing fail.
* There is no mechanism in OCPP to report back such erroneous messages.
*/
private void handleError(CommunicationContext context, String messageId, JsonParser parser) {
FutureResponseContext responseContext = futureResponseContextStore.get(context.getSession(), messageId);
if (responseContext == null) {
throw new SteveException(
"An error message was received as response to a not-sent call. The message was: %s",
context.getIncomingString()
);
}
ErrorCode code;
String desc;
String details;
try {
parser.nextToken();
code = ErrorCode.fromValue(parser.getText());
parser.nextToken();
desc = parser.getText();
// From spec:
// ErrorDescription - Should be filled in if possible, otherwise a clear empty string "".
if ("".equals(desc)) {
desc = null;
}
// From soec:
// ErrorDetails - This JSON object describes error details in an undefined way.
// If there are no error details you should fill in an empty object {}, missing or null is not allowed
details = (parser.nextToken() == JsonToken.START_OBJECT) ? null : parser.getText();
} catch (IOException e) {
throw new SteveException("Deserialization of incoming error message failed", e);
}
OcppJsonError error = new OcppJsonError();
error.setMessageId(messageId);
error.setErrorCode(code);
error.setErrorDescription(desc);
error.setErrorDetails(details);
context.setIncomingMessage(error);
context.setHandler(responseContext.getHandler());
}
}