package de.rwth.idsg.steve.ocpp.ws.pipeline;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import de.rwth.idsg.steve.SteveException;
import de.rwth.idsg.steve.ocpp.ws.ErrorFactory;
import de.rwth.idsg.steve.ocpp.ws.data.CommunicationContext;
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.OcppJsonMessage;
import de.rwth.idsg.steve.ocpp.ws.data.OcppJsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Outgoing OcppJsonMessage --> String.
*
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
* @since 17.03.2015
*/
@Slf4j
@Component
public class Serializer implements Stage {
@Autowired private ObjectMapper mapper;
@Override
public void process(CommunicationContext context) {
OcppJsonMessage message = context.getOutgoingMessage();
ArrayNode str;
MessageType messageType = message.getMessageType();
switch (messageType) {
case CALL:
str = handleCall((OcppJsonCall) message);
break;
case CALL_RESULT:
str = handleResult((OcppJsonResult) message);
break;
case CALL_ERROR:
str = handleError((OcppJsonError) message);
break;
default:
throw new SteveException("Unknown enum type");
}
try {
String result = mapper.writeValueAsString(str);
context.setOutgoingString(result);
} catch (IOException e) {
throw new SteveException("The outgoing message could not be serialized", e);
}
}
// -------------------------------------------------------------------------
// Private Helpers
// -------------------------------------------------------------------------
/**
* Do NOT catch and handle exceptions for outgoing CALLs. Do NOT send the message.
* Let the processing fail and acknowledge the user.
*/
private ArrayNode handleCall(OcppJsonCall call) {
JsonNode payloadNode;
try {
payloadNode = mapper.valueToTree(call.getPayload());
} catch (IllegalArgumentException e) {
throw new SteveException("The payload of the outgoing call could not be converted to JSON", e);
}
return mapper.createArrayNode()
.add(call.getMessageType().getTypeNr())
.add(call.getMessageId())
.add(call.getAction())
.add(payloadNode);
}
/**
* Catch exceptions and wrap them in outgoing ERRORs for outgoing RESPONSEs.
*/
private ArrayNode handleResult(OcppJsonResult result) {
JsonNode payloadNode;
try {
payloadNode = mapper.valueToTree(result.getPayload());
} catch (IllegalArgumentException e) {
log.error("Exception occurred", e);
return handleError(ErrorFactory.payloadSerializeError(result.getMessageId(), e.getMessage()));
}
return mapper.createArrayNode()
.add(result.getMessageType().getTypeNr())
.add(result.getMessageId())
.add(payloadNode);
}
/**
* No exception to catch during serialization, since the fields of the error are simple Strings.
*/
private ArrayNode handleError(OcppJsonError error) {
// From spec:
// ErrorDescription - Should be filled in if possible, otherwise a clear empty string "".
String description;
if (error.isSetDescription()) {
description = error.getErrorDescription();
} else {
description = "";
}
// 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
JsonNode detailsNode;
if (error.isSetDetails()) {
detailsNode = mapper.getNodeFactory().textNode(error.toStringErrorDetails());
} else {
detailsNode = mapper.createObjectNode();
}
return mapper.createArrayNode()
.add(error.getMessageType().getTypeNr())
.add(error.getMessageId())
.add(error.getErrorCode().name())
.add(description)
.add(detailsNode);
}
}