EventConverterFactory.java
/*
* @copyright defined in LICENSE.txt
*/
package hera.transport;
import static hera.util.TransportUtils.parseToBlockHash;
import static hera.util.TransportUtils.parseToTxHash;
import static org.slf4j.LoggerFactory.getLogger;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import hera.api.function.Function1;
import hera.api.model.AccountAddress;
import hera.api.model.BigNumber;
import hera.api.model.ContractAddress;
import hera.api.model.Event;
import hera.spec.AergoSpec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import types.Blockchain;
public class EventConverterFactory {
protected final transient Logger logger = getLogger(getClass());
protected final ObjectMapper mapper = getObjectMapper();
protected final ModelConverter<AccountAddress,
com.google.protobuf.ByteString> accountAddressConverter =
new AccountAddressConverterFactory().create();
protected final Function1<Event, Blockchain.Event> domainConverter =
new Function1<Event, Blockchain.Event>() {
@Override
public Blockchain.Event apply(final Event domainEvent) {
throw new UnsupportedOperationException();
}
};
protected final Function1<Blockchain.Event, Event> rpcConverter =
new Function1<Blockchain.Event, Event>() {
@Override
public Event apply(Blockchain.Event rpcEvent) {
logger.trace("Rpc event: {}", rpcEvent);
try {
final ContractAddress contractAddress = accountAddressConverter
.convertToDomainModel(rpcEvent.getContractAddress()).adapt(ContractAddress.class);
final CollectionType listType =
mapper.getTypeFactory().constructCollectionType(List.class, Object.class);
final List<Object> deserializedArgs =
mapper.readValue(rpcEvent.getJsonArgs(), listType);
final Event domainEvent = Event.newBuilder()
.from(contractAddress)
.name(rpcEvent.getEventName())
.args(deserializedArgs)
.index(rpcEvent.getEventIdx())
.txHash(parseToTxHash(rpcEvent.getTxHash()))
.indexInBlock(rpcEvent.getTxIndex())
.blockHash(parseToBlockHash(rpcEvent.getBlockHash()))
.blockNumber(rpcEvent.getBlockNo())
.build();
logger.trace("Rpc event converted: {}", domainEvent);
return domainEvent;
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
};
public ModelConverter<Event, Blockchain.Event> create() {
return new ModelConverter<Event, Blockchain.Event>(domainConverter,
rpcConverter);
}
protected ObjectMapper getObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
final SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(List.class, new CustomDeserializer());
objectMapper.registerModule(simpleModule);
return objectMapper;
}
protected class CustomDeserializer extends JsonDeserializer<List<Object>> {
@Override
public List<Object> deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
final List<Object> ret = new ArrayList<>();
final JsonNode jsonNode = parser.getCodec().readTree(parser);
logger.trace("Raw event args: {}", jsonNode);
if (!(jsonNode instanceof ArrayNode)) {
throw new IllegalStateException("Event args must be array but was " + jsonNode.getClass());
}
final Iterator<JsonNode> it = ((ArrayNode) jsonNode).elements();
while (it.hasNext()) {
final JsonNode next = it.next();
ret.add(parseJsonNode(next));
}
logger.trace("Parsed event args: {}", ret);
return ret;
}
protected Object parseJsonNode(final JsonNode jsonNode) {
Object ret = null;
// null, boolean, string, number
if (jsonNode.isValueNode()) {
ret = parseValueNode((ValueNode) jsonNode);
} else if (jsonNode.isArray()) {
ret = parseArrayNode((ArrayNode) jsonNode);
} else if (jsonNode.isObject()) {
ret = parseObjectNode((ObjectNode) jsonNode);
} else {
throw new IllegalArgumentException("Can't process json node " + jsonNode);
}
return ret;
}
protected Object parseValueNode(final ValueNode valueNode) {
Object ret;
if (valueNode.isNull()) {
ret = null;
} else if (valueNode.isBoolean()) {
ret = valueNode.asBoolean();
} else if (valueNode.isTextual()) {
ret = valueNode.asText();
} else if (valueNode.isNumber()) {
ret = valueNode.numberValue();
} else {
throw new IllegalArgumentException("Can't process " + valueNode);
}
return ret;
}
protected List<Object> parseArrayNode(final ArrayNode arrayNode) {
final List<Object> ret = new ArrayList<>(arrayNode.size());
final Iterator<JsonNode> it = arrayNode.elements();
while (it.hasNext()) {
final JsonNode next = it.next();
ret.add(next);
}
return ret;
}
protected Object parseObjectNode(final ObjectNode objectNode) {
final Map<String, Object> ret = new HashMap<>(objectNode.size());
if (isAergoBigNum(objectNode)) {
return parseBignumNode(objectNode);
}
final Iterator<Entry<String, JsonNode>> it = objectNode.fields();
while (it.hasNext()) {
final Entry<String, JsonNode> next = it.next();
ret.put(next.getKey(), parseJsonNode(next.getValue()));
}
return ret;
}
protected boolean isAergoBigNum(final ObjectNode objectNode) {
final JsonNode possiblyBignum = objectNode.get(AergoSpec.BIGNUM_JSON_KEY);
return objectNode.size() == 1 && null != possiblyBignum && possiblyBignum.isTextual();
}
protected BigNumber parseBignumNode(final ObjectNode objectNode) {
try {
final JsonNode bignumNode = objectNode.get(AergoSpec.BIGNUM_JSON_KEY);
return new BigNumber(bignumNode.asText());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
}