
io.bitsensor.lib.entity.util.ProtoUtils Maven / Gradle / Ivy
package io.bitsensor.lib.entity.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.bitsensor.lib.entity.proto.Detection;
import io.bitsensor.lib.entity.proto.DetectionOrBuilder;
import io.bitsensor.lib.entity.proto.Error;
import io.bitsensor.lib.entity.proto.ErrorOrBuilder;
import io.bitsensor.lib.jackson.protobuf.ProtobufDeserializer;
import io.bitsensor.lib.util.exception.DataModelException;
import io.bitsensor.proto.shaded.com.google.protobuf.InvalidProtocolBufferException;
import io.bitsensor.proto.shaded.com.google.protobuf.MessageOrBuilder;
import io.bitsensor.proto.shaded.com.google.protobuf.util.JsonFormat;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.hash.MurmurHash3;
import java.io.IOException;
import java.util.List;
import static io.bitsensor.lib.entity.Constants.*;
import static io.bitsensor.lib.entity.Constants.Detection.DETECTION_RULE_FIELDS;
import static io.bitsensor.lib.jackson.JacksonConfig.objectMapper;
import static io.bitsensor.lib.util.InputConverter.unflatten;
import static java.util.Arrays.asList;
import io.bitsensor.lib.entity.proto.Error;
/**
* Utility class for working with proto messages.
*/
public class ProtoUtils {
/**
* Fields to exclude when unflattening object.
*
* This is necessary for converting json string into protobuf message as message can have nested messages.
*/
public static final String[] UNFLATTEN_EXCLUDED_FIELDS = {CONTEXT, ENDPOINT, META, INPUT, NON_COMPLIANCE};
private static final ObjectMapper mapper = objectMapper();
public static String getValueForKey(String key, JsonObject dataPoint) {
//Get type, typically Context, Endpoint, eg. and update key accordingly
String type, path;
try {
type = key.split("\\.")[0];
path = key.substring(type.length() + 1);
} catch (Exception e) {
throw new IllegalArgumentException();
}
try {
return dataPoint.get(type).getAsJsonObject()
.get(path).getAsString();
} catch (NullPointerException | IllegalStateException notInDataPointException) {
throw new DataModelException(notInDataPointException);
}
}
/**
* Returns json string converted from a proto message.
*
* @param object a proto message
* @return json string
* @throws DataModelException when a protocol message being parsed is invalid in some way
*/
public static String proto2String(MessageOrBuilder object) {
try {
return JsonFormat.printer()
.includingDefaultValueFields()
.omittingInsignificantWhitespace()
.preservingProtoFieldNames()
.print(object);
} catch (InvalidProtocolBufferException e) {
throw new DataModelException("Unable to print MessageOrBuilder object", e);
}
}
/**
* Returns json object converted from a proto message.
*
* @param object a proto message
* @return json object
* @throws DataModelException when a protocol message being parsed is invalid in some way
*/
public static JsonObject proto2Json(MessageOrBuilder object) {
return new JsonParser().parse(proto2String(object)).getAsJsonObject();
}
/**
* Returns json object without hashes properties converted from a proto message.
*
* @param object a proto message
* @return json object
* @throws DataModelException when a protocol message being parsed is invalid in some way
*/
public static JsonObject proto2JsonWithoutHashes(MessageOrBuilder object) {
JsonObject json = new JsonParser().parse(proto2String(object)).getAsJsonObject();
json.remove("hash");
json.remove("ruleHash");
return json;
}
/**
* Returns json string compatible with elasticsearch converted from a proto message.
*
* @param object a proto message
* @return json string
* @throws DataModelException when a protocol message being parsed is invalid in some way
*/
public static String proto2EsDocument(MessageOrBuilder object) {
return unflatten(proto2String(object)).toString();
}
/**
* Returns a JSON deserialized java object from a given json element.
*/
public static T convert(JsonElement element, Class type) {
return convert(element.toString(), type);
}
/**
* Returns a JSON deserialized java object from given a json string.
*/
public static T convert(String json, Class type) {
try {
return mapper.readValue(json, type);
} catch (IOException e) {
throw new DataModelException("Unable to parse Datapoint from json '" + json + "'", e);
}
}
/**
* Returns a proto-compatible json object used for deserializing proto message.
*
* @see ProtobufDeserializer
*/
public static JsonObject toProtoJson(String json, String... exludedFields) {
JsonObject object = unflatten(json, exludedFields);
// Filter out null entry from maps
object.entrySet().stream()
.filter(entry -> entry.getValue().isJsonObject())
.map(entry -> entry.getValue().getAsJsonObject())
.forEach(map -> map.entrySet()
.removeIf(entry -> entry.getValue() == JsonNull.INSTANCE));
return object;
}
/**
* Returns instance of {@code ErrorOrBuilder} with generated hash.
*/
@SuppressWarnings("unchecked")
public static T generateHash(T object) {
if (object instanceof Error.Builder) {
return (T) ((Error.Builder) object).setHash(createHash(object));
} else if (object instanceof Error) {
return (T) generateHash(((Error) object).toBuilder()).build();
}
return object;
}
/**
* Returns instance of {@code DetectionOrBuilder} with generated hash.
*/
@SuppressWarnings("unchecked")
public static T generateHash(T object) {
if (object instanceof Detection.Builder) {
return (T) ((Detection.Builder) object).setHash(createHash(object));
} else if (object instanceof Detection) {
return (T) generateHash(((Detection) object).toBuilder()).build();
}
return object;
}
/**
* Returns instance of {@code DetectionOrBuilder} with generated rule hash.
*/
@SuppressWarnings("unchecked")
public static T generateRuleHash(T object) {
if (object.getRuleHash() != 0) {
return object;
}
if (object instanceof Detection.Builder) {
return (T) ((Detection.Builder) object).setRuleHash(createHash(object, DETECTION_RULE_FIELDS));
} else if (object instanceof Detection) {
return (T) generateRuleHash(((Detection) object).toBuilder()).build();
}
return object;
}
/**
* Returns hash for given message for fields in the {@code onlyFields} array or for all fields if the array is
* empty.
*/
private static long createHash(MessageOrBuilder message, String... onlyFields) {
JsonObject json = proto2JsonWithoutHashes(message);
final List onlyFieldsList = asList(onlyFields);
if (onlyFields.length > 0)
json.entrySet().removeIf(entry -> !onlyFieldsList.contains(entry.getKey()));
final BytesRef bytes = new BytesRef(json.toString());
return MurmurHash3.hash128(bytes.bytes, bytes.offset, bytes.length, 0, new MurmurHash3.Hash128()).h1;
}
}