EventConverterFactory.java

  1. /*
  2.  * @copyright defined in LICENSE.txt
  3.  */

  4. package hera.transport;

  5. import static hera.util.TransportUtils.parseToBlockHash;
  6. import static hera.util.TransportUtils.parseToTxHash;
  7. import static org.slf4j.LoggerFactory.getLogger;

  8. import com.fasterxml.jackson.core.JsonParser;
  9. import com.fasterxml.jackson.databind.DeserializationContext;
  10. import com.fasterxml.jackson.databind.JsonDeserializer;
  11. import com.fasterxml.jackson.databind.JsonNode;
  12. import com.fasterxml.jackson.databind.ObjectMapper;
  13. import com.fasterxml.jackson.databind.module.SimpleModule;
  14. import com.fasterxml.jackson.databind.node.ArrayNode;
  15. import com.fasterxml.jackson.databind.node.ObjectNode;
  16. import com.fasterxml.jackson.databind.node.ValueNode;
  17. import com.fasterxml.jackson.databind.type.CollectionType;
  18. import hera.api.function.Function1;
  19. import hera.api.model.AccountAddress;
  20. import hera.api.model.BigNumber;
  21. import hera.api.model.ContractAddress;
  22. import hera.api.model.Event;
  23. import hera.spec.AergoSpec;
  24. import java.io.IOException;
  25. import java.util.ArrayList;
  26. import java.util.HashMap;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Map.Entry;
  31. import org.slf4j.Logger;
  32. import types.Blockchain;

  33. public class EventConverterFactory {

  34.   protected final transient Logger logger = getLogger(getClass());

  35.   protected final ObjectMapper mapper = getObjectMapper();

  36.   protected final ModelConverter<AccountAddress,
  37.       com.google.protobuf.ByteString> accountAddressConverter =
  38.           new AccountAddressConverterFactory().create();

  39.   protected final Function1<Event, Blockchain.Event> domainConverter =
  40.       new Function1<Event, Blockchain.Event>() {

  41.         @Override
  42.         public Blockchain.Event apply(final Event domainEvent) {
  43.           throw new UnsupportedOperationException();
  44.         }
  45.       };

  46.   protected final Function1<Blockchain.Event, Event> rpcConverter =
  47.       new Function1<Blockchain.Event, Event>() {

  48.         @Override
  49.         public Event apply(Blockchain.Event rpcEvent) {
  50.           logger.trace("Rpc event: {}", rpcEvent);

  51.           try {
  52.             final ContractAddress contractAddress = accountAddressConverter
  53.                 .convertToDomainModel(rpcEvent.getContractAddress()).adapt(ContractAddress.class);
  54.             final CollectionType listType =
  55.                 mapper.getTypeFactory().constructCollectionType(List.class, Object.class);
  56.             final List<Object> deserializedArgs =
  57.                 mapper.readValue(rpcEvent.getJsonArgs(), listType);
  58.             final Event domainEvent = Event.newBuilder()
  59.                 .from(contractAddress)
  60.                 .name(rpcEvent.getEventName())
  61.                 .args(deserializedArgs)
  62.                 .index(rpcEvent.getEventIdx())
  63.                 .txHash(parseToTxHash(rpcEvent.getTxHash()))
  64.                 .indexInBlock(rpcEvent.getTxIndex())
  65.                 .blockHash(parseToBlockHash(rpcEvent.getBlockHash()))
  66.                 .blockNumber(rpcEvent.getBlockNo())
  67.                 .build();
  68.             logger.trace("Rpc event converted: {}", domainEvent);
  69.             return domainEvent;
  70.           } catch (Exception e) {
  71.             throw new IllegalArgumentException(e);
  72.           }
  73.         }
  74.       };

  75.   public ModelConverter<Event, Blockchain.Event> create() {
  76.     return new ModelConverter<Event, Blockchain.Event>(domainConverter,
  77.         rpcConverter);
  78.   }

  79.   protected ObjectMapper getObjectMapper() {
  80.     final ObjectMapper objectMapper = new ObjectMapper();

  81.     final SimpleModule simpleModule = new SimpleModule();
  82.     simpleModule.addDeserializer(List.class, new CustomDeserializer());
  83.     objectMapper.registerModule(simpleModule);

  84.     return objectMapper;
  85.   }

  86.   protected class CustomDeserializer extends JsonDeserializer<List<Object>> {

  87.     @Override
  88.     public List<Object> deserialize(JsonParser parser, DeserializationContext context)
  89.         throws IOException {
  90.       final List<Object> ret = new ArrayList<>();

  91.       final JsonNode jsonNode = parser.getCodec().readTree(parser);
  92.       logger.trace("Raw event args: {}", jsonNode);
  93.       if (!(jsonNode instanceof ArrayNode)) {
  94.         throw new IllegalStateException("Event args must be array but was " + jsonNode.getClass());
  95.       }

  96.       final Iterator<JsonNode> it = ((ArrayNode) jsonNode).elements();
  97.       while (it.hasNext()) {
  98.         final JsonNode next = it.next();
  99.         ret.add(parseJsonNode(next));
  100.       }
  101.       logger.trace("Parsed event args: {}", ret);

  102.       return ret;
  103.     }

  104.     protected Object parseJsonNode(final JsonNode jsonNode) {
  105.       Object ret = null;

  106.       // null, boolean, string, number
  107.       if (jsonNode.isValueNode()) {
  108.         ret = parseValueNode((ValueNode) jsonNode);
  109.       } else if (jsonNode.isArray()) {
  110.         ret = parseArrayNode((ArrayNode) jsonNode);
  111.       } else if (jsonNode.isObject()) {
  112.         ret = parseObjectNode((ObjectNode) jsonNode);
  113.       } else {
  114.         throw new IllegalArgumentException("Can't process json node " + jsonNode);
  115.       }

  116.       return ret;
  117.     }

  118.     protected Object parseValueNode(final ValueNode valueNode) {
  119.       Object ret;

  120.       if (valueNode.isNull()) {
  121.         ret = null;
  122.       } else if (valueNode.isBoolean()) {
  123.         ret = valueNode.asBoolean();
  124.       } else if (valueNode.isTextual()) {
  125.         ret = valueNode.asText();
  126.       } else if (valueNode.isNumber()) {
  127.         ret = valueNode.numberValue();
  128.       } else {
  129.         throw new IllegalArgumentException("Can't process " + valueNode);
  130.       }

  131.       return ret;
  132.     }

  133.     protected List<Object> parseArrayNode(final ArrayNode arrayNode) {
  134.       final List<Object> ret = new ArrayList<>(arrayNode.size());

  135.       final Iterator<JsonNode> it = arrayNode.elements();
  136.       while (it.hasNext()) {
  137.         final JsonNode next = it.next();
  138.         ret.add(next);
  139.       }

  140.       return ret;
  141.     }

  142.     protected Object parseObjectNode(final ObjectNode objectNode) {
  143.       final Map<String, Object> ret = new HashMap<>(objectNode.size());

  144.       if (isAergoBigNum(objectNode)) {
  145.         return parseBignumNode(objectNode);
  146.       }

  147.       final Iterator<Entry<String, JsonNode>> it = objectNode.fields();
  148.       while (it.hasNext()) {
  149.         final Entry<String, JsonNode> next = it.next();
  150.         ret.put(next.getKey(), parseJsonNode(next.getValue()));
  151.       }

  152.       return ret;
  153.     }

  154.     protected boolean isAergoBigNum(final ObjectNode objectNode) {
  155.       final JsonNode possiblyBignum = objectNode.get(AergoSpec.BIGNUM_JSON_KEY);
  156.       return objectNode.size() == 1 && null != possiblyBignum && possiblyBignum.isTextual();
  157.     }

  158.     protected BigNumber parseBignumNode(final ObjectNode objectNode) {
  159.       try {
  160.         final JsonNode bignumNode = objectNode.get(AergoSpec.BIGNUM_JSON_KEY);
  161.         return new BigNumber(bignumNode.asText());
  162.       } catch (Exception e) {
  163.         throw new IllegalArgumentException(e);
  164.       }
  165.     }

  166.   }

  167. }