All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.gateway.core.GatewayMessageCodec Maven / Gradle / Ivy

There is a newer version: 2.0.10
Show newest version
package io.scalecube.gateway.core;

import static com.fasterxml.jackson.core.JsonToken.VALUE_NULL;
import static io.scalecube.gateway.core.GatewayMessage.DATA_FIELD;
import static io.scalecube.gateway.core.GatewayMessage.INACTIVITY_FIELD;
import static io.scalecube.gateway.core.GatewayMessage.QUALIFIER_FIELD;
import static io.scalecube.gateway.core.GatewayMessage.SIGNAL_FIELD;
import static io.scalecube.gateway.core.GatewayMessage.STREAM_ID_FIELD;

import io.scalecube.services.exceptions.MessageCodecException;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.util.ReferenceCountUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

/**
 * Encodes/decodes {@link GatewayMessage} to/from {@link ByteBuf}.
 */
public class GatewayMessageCodec {
  private static final Logger LOGGER = LoggerFactory.getLogger(GatewayMessageCodec.class);

  private static final ObjectMapper objectMapper = objectMapper();

  private static final MappingJsonFactory jsonFactory = new MappingJsonFactory(objectMapper);

  /**
   * Encode given {@code message} to given {@code byteBuf}
   *
   * @param message - input message to be encoded.
   * @throws MessageCodecException in case of issues during encoding.
   */
  public ByteBuf encode(GatewayMessage message) throws MessageCodecException {
    ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
    try (
        JsonGenerator generator =
            jsonFactory.createGenerator((OutputStream) new ByteBufOutputStream(byteBuf), JsonEncoding.UTF8)) {
      generator.writeStartObject();

      if (message.qualifier() != null) {
        generator.writeStringField(QUALIFIER_FIELD, message.qualifier());
      }
      if (message.streamId() != null) {
        generator.writeNumberField(STREAM_ID_FIELD, message.streamId());
      }
      if (message.signal() != null) {
        generator.writeNumberField(SIGNAL_FIELD, message.signal());
      }

      if (message.inactivity() != null) {
        generator.writeNumberField(INACTIVITY_FIELD, message.inactivity());
      }

      // data
      Object data = message.data();
      if (data != null) {
        if (data instanceof ByteBuf) {
          ByteBuf dataBin = (ByteBuf) data;
          if (dataBin.readableBytes() > 0) {
            generator.writeFieldName(DATA_FIELD);
            generator.writeRaw(":");
            generator.flush();
            byteBuf.writeBytes(dataBin);
          }
        } else {
          generator.writeObjectField(DATA_FIELD, data);
        }
      }

      generator.writeEndObject();
    } catch (Throwable ex) {
      ReferenceCountUtil.safeRelease(byteBuf);
      LOGGER.error("Failed to encode message: {}", message, ex);
      throw new MessageCodecException("Failed to encode message", ex);
    }
    return byteBuf;
  }


  /**
   * Decodes {@link GatewayMessage} from given {@code byteBuf}.
   *
   * @param byteBuf - contains raw {@link GatewayMessage} to be decoded.
   *
   * @return Decoded {@link GatewayMessage}.
   * @throws MessageCodecException - in case of issues during deserialization.
   */
  public GatewayMessage decode(ByteBuf byteBuf) throws MessageCodecException {
    try (InputStream stream = new ByteBufInputStream(byteBuf.slice())) {
      JsonParser jp = jsonFactory.createParser(stream);
      GatewayMessage.Builder result = GatewayMessage.builder();

      JsonToken current = jp.nextToken();
      if (current != JsonToken.START_OBJECT) {
        LOGGER.error("Root should be object: {}", byteBuf.toString(Charset.defaultCharset()));
        throw new MessageCodecException("Root should be object", null);
      }
      long dataStart = 0;
      long dataEnd = 0;
      while ((jp.nextToken()) != JsonToken.END_OBJECT) {
        String fieldName = jp.getCurrentName();
        current = jp.nextToken();
        if (current == VALUE_NULL) {
          continue;
        }
        switch (fieldName) {
          case QUALIFIER_FIELD:
            result.qualifier(jp.getValueAsString());
            break;
          case STREAM_ID_FIELD:
            result.streamId(jp.getValueAsLong());
            break;
          case SIGNAL_FIELD:
            result.signal(jp.getValueAsInt());
            break;
          case INACTIVITY_FIELD:
            result.inactivity(jp.getValueAsInt());
            break;
          case DATA_FIELD:
            dataStart = jp.getTokenLocation().getByteOffset();
            if (current.isScalarValue()) {
              if (!current.isNumeric() && !current.isBoolean()) {
                jp.getValueAsString();
              }
            } else if (current.isStructStart()) {
              jp.skipChildren();
            }
            dataEnd = jp.getCurrentLocation().getByteOffset();
            break;
          default:
            break;
        }
      }
      if (dataEnd > dataStart) {
        result.data(byteBuf.slice((int) dataStart, (int) (dataEnd - dataStart)));
      }
      return result.build();
    } catch (Throwable ex) {
      LOGGER.error("Failed to decode message: {}", byteBuf.toString(Charset.defaultCharset()), ex);
      throw new MessageCodecException("Failed to decode message", ex);
    }
  }

  private static ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy