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

com.uid2.shared.optout.OptOutUtils Maven / Gradle / Ivy

package com.uid2.shared.optout;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.uid2.shared.Const;
import com.uid2.shared.Utils;
import com.uid2.shared.cloud.CloudUtils;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

public class OptOutUtils {
    // delta file name pattern: optout-delta-_yyyy-MM-ddTHH:mm:ssZ.dat
    public static final String prefixDeltaFile = "optout-delta-";
    // partition file name pattern: optout-partition-_yyyy-MM-ddTHH:MM:SSZ.dat
    public static final String prefixPartitionFile = "optout-partition-";
    private static final Logger LOGGER = LoggerFactory.getLogger(OptOutUtils.class);

    public static Random rand = new Random();
    public static ObjectMapper mapper = new ObjectMapper();
    public static String tmpDir = System.getProperty("java.io.tmpdir");

    public static Base64.Encoder base64Encoder = Base64.getEncoder();
    public static Base64.Decoder base64Decoder = Base64.getDecoder();

    public static byte[] nullHashBytes = new byte[OptOutConst.Sha256Bytes];
    public static String nullHash = OptOutUtils.byteArrayToHex(new byte[OptOutConst.Sha256Bytes]);

    public static String onesHash = new String(new char[OptOutConst.Sha256Characters]).replace("\0", "f");
    public static byte[] onesHashBytes = OptOutUtils.hexToByteArray(OptOutUtils.onesHash);

    // comparator to help producing a sorted order of delta filenames
    public static final Comparator DeltaFilenameComparator = new Comparator() {
        @Override
        public int compare(String f1, String f2) {
            try {
                return (int) (OptOutUtils.getFileTimestamp(f1).getEpochSecond() - OptOutUtils.getFileTimestamp(f2).getEpochSecond());
            } catch (Exception ex) {
                LOGGER.error("unexpected exception for delta filename comparison: " + ex.getMessage());
                return f1.compareTo(f2);
            }
        }
    };

    // comparator to help producing a descending sorted order of delta filenames
    public static final Comparator DeltaFilenameComparatorDescending = new Comparator() {
        @Override
        public int compare(String f1, String f2) {
            try {
                return (int) (OptOutUtils.getFileTimestamp(f2).getEpochSecond() - OptOutUtils.getFileTimestamp(f1).getEpochSecond());
            } catch (Exception ex) {
                LOGGER.error("unexpected exception for delta filename comparison: " + ex.getMessage());
                return f2.compareTo(f1);
            }
        }
    };

    public static boolean isHexidecimal(char c) {
        // assuming ascii encoding
        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    }

    public static boolean isValidSha256Hex(String sha256Hex) {
        if (sha256Hex.length() != OptOutConst.Sha256Characters)
            return false;

        for (int i = 0; i < OptOutConst.Sha256Characters; ++i) {
            if (!isHexidecimal(sha256Hex.charAt(i))) return false;
        }
        return true;
    }

    public static byte[] hexToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    public static String byteArrayToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes)
            sb.append(String.format("%02x", b));
        return sb.toString();
    }

    public static String byteArrayToBase64String(byte[] bytes) {
        return base64Encoder.encodeToString(bytes);
    }

    public static byte[] base64StringTobyteArray(String b64) {
        try {
            return base64Decoder.decode(b64);
        } catch (IllegalArgumentException ex) {
            // base64 decoder throws if input is invalid, returning null for such case
            return null;
        }
    }

    public static int logTwo(int val) {
        assert val > 0;
        int powerOfTwo = 0;
        while (val != 1) {
            ++powerOfTwo;
            val >>= 1;
        }
        return powerOfTwo;
    }

    public static String[] jsonArrayToStringArray(String json) {
        try {
            return mapper.readValue(json, String[].class);
        } catch (Exception ex) {
            // this is internal message and not expected to be invalid, returning null
            return null;
        }
    }

    public static String toJson(String... strs) {
        return OptOutUtils.toJson(Arrays.asList(strs));
    }

    public static String toJson(Collection strs) {
        try {
            return mapper.writeValueAsString(strs);
        } catch (Exception ex) {
            // this is internal message and not expected to be invalid, returning null
            return null;
        }
    }

    public static  List toList(T... array) {
        return Arrays.stream(array).collect(Collectors.toList());
    }

    public static List toList(long[] array) {
        return Arrays.stream(array).boxed().collect(Collectors.toList());
    }

    public static long[] toLongArray(long... nums) {
        return nums;
    }

    public static  T[] toArray(T... array) {
        return array;
    }

    public static long[] toArray(List list) {
        return list.stream().mapToLong(l -> l).toArray();
    }

    public static Set toSet(long[] array) {
        return Arrays.stream(array).boxed().collect(Collectors.toSet());
    }

    public static  HashSet toSet(T[] array) {
        return new HashSet<>(Arrays.asList(array));
    }

    public static byte[] toByteArray(int num) {
        return ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(num).array();
    }

    public static byte[] toByteArray(long num) {
        return ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN).putLong(num).array();
    }

    public static byte[] toByteArrayBE(long num) {
        return ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN).putLong(num).array();
    }

    public static int toInt(byte[] bytes, int byteIndex) {
        assert byteIndex >= 0 && byteIndex + Integer.BYTES <= bytes.length;
        int val = 0;
        for (int i = 0; i < Integer.BYTES; i++) {
            val += ((int) bytes[byteIndex + i] & 0xffL) << (8 * i);
        }
        return val;
    }

    public static long toLong(byte[] bytes, int byteIndex) {
        assert byteIndex >= 0 && byteIndex + Long.BYTES <= bytes.length;
        long val = 0;
        for (int i = 0; i < Long.BYTES; i++) {
            val += ((long) bytes[byteIndex + i] & 0xffL) << (8 * i);
        }
        return val;
    }

    public static long toLongBE(byte[] bytes, int byteIndex) {
        return ByteBuffer.wrap(bytes, 0, Long.BYTES).order(ByteOrder.BIG_ENDIAN).getLong();
    }

    // positive if arr1 > arr2
    // negative if arr1 < arr2
    // 0 if arr1 == arr2
    public static int compareBytes(byte[] arr1, byte[] arr2) {
        assert arr1.length == arr2.length;
        return OptOutUtils.compareByteRange(arr1, 0, arr2, 0, arr1.length);
    }

    // positive if arr1 > arr2
    // negative if arr1 < arr2
    // 0 if arr1 == arr2
    public static int compareByteRange(byte[] arr1, int idx1, byte[] arr2, int idx2, int bytesToCompare) {
        assert idx1 >= 0 && idx1 + bytesToCompare <= arr1.length;
        assert idx2 >= 0 && idx2 + bytesToCompare <= arr2.length;
        for (int i = 0; i < bytesToCompare; ++i) {
            int cmp = OptOutUtils.compareByte(arr1[idx1 + i], arr2[idx2 + i]);
            if (cmp != 0) return cmp;
        }
        return 0;
    }

    public static int compareByte(byte a, byte b) {
        return Byte.toUnsignedInt(a) - Byte.toUnsignedInt(b);
    }

    public static boolean isDeltaFile(String fn) {
        return fn.contains(OptOutUtils.prefixDeltaFile);
    }

    public static boolean isPartitionFile(String fn) {
        return fn.contains(OptOutUtils.prefixPartitionFile);
    }

    public static long getFileEpochSeconds(String fullPath) {
        return OptOutUtils.getFileTimestamp(fullPath).getEpochSecond();
    }

    public static int getFileReplicaId(Path path) {
        // file name forpath optout-partition-999_2021-04-21T00.00.00Z_014e024f.dat
        String fn = path.getFileName().toString();
        int indexOfSep = fn.indexOf('_');
        if (indexOfSep == -1) return -1;

        // optout-partition-999
        String fileTypeAndReplicaId = fn.substring(0, indexOfSep);
        int lastIdxOfDash = fileTypeAndReplicaId.lastIndexOf("-");
        if (lastIdxOfDash == -1) return -1;

        // 999
        return Integer.valueOf(fileTypeAndReplicaId.substring(lastIdxOfDash + 1));
    }

    public static Instant getFileTimestamp(String fullPath) {
        if (fullPath.startsWith("http")) {
            fullPath = extractUrlPath(fullPath);
        }
        return OptOutUtils.getFileTimestamp(Paths.get(fullPath));
    }

    public static String extractUrlPath(String urlPath) {
        // remove query and extract path from URL
        try {
            URL url = new URL(urlPath);
            // return urlPath.replace("?" + url.getQuery(), "");
            return url.getPath();
        } catch (MalformedURLException e) {
            return urlPath;
        }
    }

    public static Instant getFileTimestamp(Path path) {
        String fileName = path.getFileName().toString();

        int indexOfSep = fileName.indexOf('_');
        if (indexOfSep == -1) return null;

        String timestamp = fileName.substring(indexOfSep + 1);
        int indexOfSep2 = timestamp.indexOf('_');
        if (indexOfSep2 == -1) return null;

        // yyyy-MM-ddTHH,mm,ssZ -> yyyy-MM-ddTHH:mm:ssZ
        timestamp = timestamp.substring(0, indexOfSep2);
        timestamp = timestamp.replace('.', ':');

        try {
            return Instant.parse(timestamp);
        } catch (Exception ex) {
            return null;
        }
    }

    public static long nowEpochSeconds() {
        return Instant.now().getEpochSecond();
    }

    // yyyy-MM-ddTHH:mm:ssZ -> yyyy-MM-ddTHH,mm,ssZ
    public static String timestampNowEscaped() {
        return timestampEscaped(Instant.now().truncatedTo(ChronoUnit.SECONDS));
    }

    public static String timestampEscaped(Instant ts) {
        return ts.toString().replace(':', '.');
    }

    public static String newDeltaFileName(int replicaId) {
        String ts = timestampNowEscaped();
        return OptOutUtils.newDeltaFileName(ts, replicaId);
    }

    public static String newPartitionFileName(int replicaId) {
        String ts = timestampNowEscaped();
        return OptOutUtils.newPartitionFileName(ts, replicaId);
    }

    public static String newDeltaFileName(Instant now) {
        String ts = timestampEscaped(now);
        return OptOutUtils.newDeltaFileName(ts, 0);
    }

    public static String newPartitionFileName(Instant now) {
        String ts = timestampEscaped(now);
        return OptOutUtils.newPartitionFileName(ts, 0);
    }

    public static String newDeltaFileName(String ts, int replicaId) {
        return String.format("%s%03d_%s_%08x.dat", OptOutUtils.prefixDeltaFile, replicaId, ts, OptOutUtils.rand.nextInt());
    }

    public static String newPartitionFileName(String ts, int replicaId) {
        return String.format("%s%03d_%s_%08x.dat", OptOutUtils.prefixPartitionFile, replicaId, ts, OptOutUtils.rand.nextInt());
    }

    public static int getReplicaIdFromHostName(String hostname) {
        try {
            if (hostname.indexOf('.') > 0) {
                hostname = hostname.substring(0, hostname.indexOf('.'));
            }
            return Integer.valueOf(hostname.substring(hostname.lastIndexOf('-') + 1));
        } catch (Exception e) {
            LOGGER.error("unable to parse replica_id from hostname " + hostname);
            return 0;
        }
    }

    public static String getDateStr(Instant time) {
        String ts = time.toString(); // 2020-12-06T02:49:39.606119Z
        return ts.substring(0, 10);
    }

    public static int getReplicaId(JsonObject jsonConfig) {
        int replicaId = -1;
        try {
            replicaId = jsonConfig.getInteger(Const.Config.OptOutProducerReplicaIdProp);
        } catch (ClassCastException e) {
            replicaId = Integer.valueOf(jsonConfig.getString(Const.Config.OptOutProducerReplicaIdProp));
        } catch (NullPointerException e) {
            replicaId = -1;
        }

        int offset = 0;
        try {
            offset = jsonConfig.getInteger(Const.Config.OptOutProducerReplicaIdOffsetProp);
        } catch (ClassCastException e) {
            offset = Integer.valueOf(jsonConfig.getString(Const.Config.OptOutProducerReplicaIdOffsetProp));
        } catch (NullPointerException e) {
            offset = 0;
        }

        if (Utils.isProductionEnvironment() && replicaId == -1) {
            // read from hostname
            try {
                String hostname = InetAddress.getLocalHost().getHostName();
                replicaId = offset + OptOutUtils.getReplicaIdFromHostName(hostname);
            } catch (UnknownHostException e) {
                LOGGER.error("unable to retrieve hostname", e);
                replicaId = offset;
            }
        }

        return replicaId;
    }

    // this method assumes the list is sorted using comparator, and will insert the new value into the proper place
    public static void addSorted(LinkedList list, String filename, Comparator comparator) {
        ListIterator iterator = list.listIterator();
        while (iterator.hasNext()) {
            // find the iterator to insert the filename
            if (comparator.compare(iterator.next(), filename) > 0) {
                // move back one iterator if it is not the first
                if (iterator.hasPrevious()) iterator.previous();
                break;
            }
        }
        iterator.add(filename);
    }

    public static Instant instantFloorByInterval(Instant now, int interval) {
        long minusSeconds = interval - getSecondsBeforeNextSlot(now, interval);
        return Instant.ofEpochSecond(now.getEpochSecond() - minusSeconds);
    }

    public static int getSecondsBeforeNextSlot(Instant now, int slotInterval) {
        if (slotInterval < 1) return 0;

        long secondsOfDay = now.getEpochSecond() - now.truncatedTo(ChronoUnit.DAYS).getEpochSecond();
        assert secondsOfDay >= 0;
        long secondsOfNextSlot = ((secondsOfDay + slotInterval - 1) / slotInterval) * slotInterval;
        assert secondsOfNextSlot >= secondsOfDay;
        return (int) (secondsOfNextSlot - secondsOfDay);
    }

    public static Future readTimestampFromFile(Vertx vertx, Path filePath, long defaultValue) {
        Promise promise = Promise.promise();
        vertx.executeBlocking(blockPromise -> {
            // create parent directory if not existing
            Utils.ensureDirectoryExists(filePath.getParent());

            if (Files.exists(filePath)) {
                try {
                    List lines = Files.readAllLines(filePath);
                    if (lines.size() > 0) {
                        Long epochSeconds = Long.valueOf(lines.get(0));
                        blockPromise.complete(epochSeconds);
                        return;
                    }
                } catch (IOException e) {
                    blockPromise.fail(new Throwable(e));
                    return;
                }
            }
            try {
                Files.write(filePath, String.valueOf(defaultValue).getBytes(), StandardOpenOption.CREATE);
                blockPromise.complete(defaultValue);
            } catch (IOException e) {
                blockPromise.fail(new Throwable(e));
            }
        }, ar -> {
            promise.handle(ar);
        });
        return promise.future();
    }

    public static Future writeTimestampToFile(Vertx vertx, Path filePath, long timestamp) {
        Promise promise = Promise.promise();
        vertx.executeBlocking(blockPromise -> {
            // create parent directory if not existing
            Utils.ensureDirectoryExists(filePath.getParent());

            try {
                Files.write(filePath, String.valueOf(timestamp).getBytes());
                blockPromise.complete();
            } catch (IOException e) {
                blockPromise.fail(new Throwable(e));
            }
        }, ar -> {
            promise.handle(ar);
        });
        return promise.future();
    }

    public static Future appendLinesToFile(Vertx vertx, Path filePath, List lines) {
        Promise promise = Promise.promise();
        vertx.executeBlocking(blockPromise -> {
            // create parent directory if not existing
            Utils.ensureDirectoryExists(filePath.getParent());
            // create empty file
            Utils.ensureFileExists(filePath);

            try {
                // append an empty line
                lines.add("");
                byte[] bytes = String.join("\n", lines).getBytes();
                Files.write(filePath, bytes, StandardOpenOption.APPEND);
                blockPromise.complete();
            } catch (IOException e) {
                blockPromise.fail(new Throwable(e));
            }
        }, ar -> {
            promise.handle(ar);
        });
        return promise.future();
    }

    public static Future readLinesFromFile(Vertx vertx, Path filePath) {
        Promise promise = Promise.promise();
        vertx.executeBlocking(blockPromise -> {
            // create parent directory if not existing
            Utils.ensureDirectoryExists(filePath.getParent());

            try {
                if (Files.exists(filePath)) {
                    String[] lines = Files.readAllLines(filePath).stream().toArray(String[]::new);
                    blockPromise.complete(lines);
                } else {
                    blockPromise.complete(new String[0]);
                }
            } catch (IOException e) {
                blockPromise.fail(new Throwable(e));
            }
        }, ar -> {
            promise.handle(ar);
        });
        return promise.future();
    }

    // last partition file timestamp
    public static Instant lastPartitionTimestamp(Collection collection) {
        Optional tsOfLast = collection.stream()
            .filter(p -> OptOutUtils.isPartitionFile(p))
            .map(OptOutUtils::getFileTimestamp)
            .sorted(Comparator.reverseOrder()).findFirst();

        return tsOfLast.isPresent() ? tsOfLast.get() : Instant.EPOCH;
    }

    public static boolean isDeltaBeforePartition(Instant tsOfSnap, String deltaFile) {
        Instant ts = OptOutUtils.getFileTimestamp(deltaFile);
        if (ts == null) return true;
        else return ts.isBefore(tsOfSnap);
    }

    public static boolean isSyntheticFile(String fullPath) {
        if (fullPath.startsWith("https")) {
            fullPath = extractUrlPath(fullPath);
        }

        return OptOutUtils.getFileReplicaId(Paths.get(fullPath)) == 999;
    }

    public static String getDeltaConsumerDir(JsonObject config) {
        return String.format("%sconsumer/delta",
            CloudUtils.normalizDirPath(config.getString(Const.Config.OptOutDataDirProp)));
    }

    public static String getPartitionConsumerDir(JsonObject config) {
        return String.format("%sconsumer/partition",
            CloudUtils.normalizDirPath(config.getString(Const.Config.OptOutDataDirProp)));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy