cloud.eppo.Utils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdk-common-jvm Show documentation
Show all versions of sdk-common-jvm Show documentation
Eppo SDK for JVM shared library
package cloud.eppo;
import com.fasterxml.jackson.databind.JsonNode;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Utils {
private static final SimpleDateFormat UTC_ISO_DATE_FORMAT = buildUtcIsoDateFormat();
private static final Logger log = LoggerFactory.getLogger(Utils.class);
private static final MessageDigest md = buildMd5MessageDigest();
private static MessageDigest buildMd5MessageDigest() {
try {
return MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error computing md5 hash", e);
}
}
public static void throwIfEmptyOrNull(String input, String errorMessage) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException(errorMessage);
}
}
/**
* Return the String representation of the zero-padded hexadecimal hash of a string input This is
* useful for comparing against other string hashes, such as obfuscated flag names.
*/
public static String getMD5Hex(String input) {
// md5 the input
md.reset();
byte[] md5Bytes = md.digest(input.getBytes());
// Pre-allocate a StringBuilder with a capacity of 32 characters
StringBuilder hexString = new StringBuilder(32);
for (byte b : md5Bytes) {
// Append the two hex digits corresponding to the byte
hexString.append(Character.forDigit((b >> 4) & 0xF, 16));
hexString.append(Character.forDigit(b & 0xF, 16));
}
return hexString.toString();
}
/**
* Return a deterministic pseudo-random integer based on the input that falls between 0
* (inclusive) and a max value (exclusive) This is useful for randomly bucketing subjects or
* shuffling bandit actions
*/
public static int getShard(String input, int maxShardValue) {
// md5 the input
md.reset();
byte[] md5Bytes = md.digest(input.getBytes());
// Extract the first 4 bytes (8 digits) and convert to a long
long value = 0;
for (int i = 0; i < 4; i++) {
value = (value << 8) | (md5Bytes[i] & 0xFF);
}
// Modulo into the shard space
return (int) (value % maxShardValue);
}
public static Date parseUtcISODateNode(JsonNode isoDateStringElement) {
if (isoDateStringElement == null || isoDateStringElement.isNull()) {
return null;
}
String isoDateString = isoDateStringElement.asText();
Date result = null;
try {
result = UTC_ISO_DATE_FORMAT.parse(isoDateString);
} catch (ParseException e) {
// We expect to fail parsing if the date is base 64 encoded
// Thus we'll leave the result null for now and try again with the decoded value
}
if (result == null) {
// Date may be encoded
String decodedIsoDateString = base64Decode(isoDateString);
try {
result = UTC_ISO_DATE_FORMAT.parse(decodedIsoDateString);
} catch (ParseException e) {
log.warn("Date \"{}\" not in ISO date format", isoDateString);
}
}
return result;
}
public static String getISODate(Date date) {
return UTC_ISO_DATE_FORMAT.format(date);
}
public static String base64Encode(String input) {
if (input == null) {
return null;
}
return Base64.encodeBase64String(input.getBytes(StandardCharsets.UTF_8));
}
public static String base64Decode(String input) {
if (input == null) {
return null;
}
byte[] decodedBytes = Base64.decodeBase64(input);
if (decodedBytes.length == 0 && !input.isEmpty()) {
throw new RuntimeException(
"zero byte output from Base64; if not running on Android hardware be sure to use RobolectricTestRunner");
}
return new String(decodedBytes);
}
private static SimpleDateFormat buildUtcIsoDateFormat() {
// Note: we don't use DateTimeFormatter.ISO_DATE so that this supports older Android versions
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
dateFormat.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
return dateFormat;
}
}