co.cask.cdap.common.app.RunIds.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.common.app.RunIds.java

Source

/*
 * Copyright  2015 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package co.cask.cdap.common.app;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.primitives.Longs;
import org.apache.twill.api.RunId;

import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Generates an unique ID for a running program using type 1 and variant 2 time-based {@link UUID}.
 * This implements time-based UUID generation algorithm described in
 * <a href="http://www.ietf.org/rfc/rfc4122.txt">A Universally Unique IDentifier (UUID) URN Namespace</a>
 * with the following modifications:
 * <ul>
 *   <li>It does not share state with other instances of time-based UUID generators on a given machine. So it is
 *   recommended not to run more than one instance of this on a machine to guarantee uniqueness of UUIDs generated.</li>
 *   <li>The timestamp embedded in the UUID is only valid up to millisecond precision. This is because this
 *   implementation uses a counter value in the remaining bits to ensure unique UUIDs are generated when invoked
 *   multiple times at the same millisecond (up to 10000 times).</li>
 * </ul>
 */
public final class RunIds {
    private static final Random RANDOM = new Random();

    // Number of 100ns intervals since 15 October 1582 00:00:000000000 till UNIX epoch
    private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L;
    // Multiplier to convert millisecond into 100ns
    private static final long HUNDRED_NANO_MULTIPLIER = 10000;

    private static final AtomicLong COUNTER = new AtomicLong();

    /**
     * @return UUID based on current time. If called repeatedly within the same millisecond, this is
     * guaranteed to generate at least 10000 unique UUIDs for the millisecond.
     */
    public static RunId generate() {
        return new RunIdImpl(generateUUIDForTime(System.currentTimeMillis()));
    }

    /**
     * Converts string representation of run id into {@link RunId}l
     */
    public static RunId fromString(String id) {
        return new RunIdImpl(UUID.fromString(id));
    }

    /**
     * Returns time-based UUID for given time. This method is recommended to be used only for testing.
     * @param timeMillis time since epoch
     * @return time-based UUID.
     */
    @VisibleForTesting
    public static RunId generate(long timeMillis) {
        return new RunIdImpl(generateUUIDForTime(timeMillis));
    }

    /**
     * @return time from the UUID if it is a time-based UUID, -1 otherwise.
     */
    public static long getTime(RunId runId, TimeUnit timeUnit) {
        UUID uuid = UUID.fromString(runId.getId());
        if (uuid.version() == 1 && uuid.variant() == 2) {
            long timeInMilliseconds = (uuid.timestamp() - NUM_100NS_INTERVALS_SINCE_UUID_EPOCH)
                    / HUNDRED_NANO_MULTIPLIER;
            return timeUnit.convert(timeInMilliseconds, TimeUnit.MILLISECONDS);
        }
        return -1;
    }

    @VisibleForTesting
    static UUID generateUUIDForTime(long timeInMillis) {
        // Use system time in milliseconds to generate time in 100ns.
        // Use COUNTER to ensure unique time gets generated for the same millisecond (up to HUNDRED_NANO_MULTIPLIER)
        // Hence the time is valid only for millisecond precision, event though it represents 100ns precision.
        long ts = timeInMillis * HUNDRED_NANO_MULTIPLIER + COUNTER.incrementAndGet() % HUNDRED_NANO_MULTIPLIER;
        long time = ts + NUM_100NS_INTERVALS_SINCE_UUID_EPOCH;

        long timeLow = time & 0xffffffffL;
        long timeMid = time & 0xffff00000000L;
        long timeHi = time & 0xfff000000000000L;
        long upperLong = (timeLow << 32) | (timeMid >> 16) | (1 << 12) | (timeHi >> 48);

        // Random clock ID
        int clockId = RANDOM.nextInt() & 0x3FFF;
        long nodeId;

        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            NetworkInterface networkInterface = null;
            while (interfaces.hasMoreElements()) {
                networkInterface = interfaces.nextElement();
                if (!networkInterface.isLoopback()) {
                    break;
                }
            }
            byte[] mac = networkInterface == null ? null : networkInterface.getHardwareAddress();
            if (mac == null) {
                nodeId = (RANDOM.nextLong() & 0xFFFFFFL) | 0x100000L;
            } else {
                nodeId = Longs.fromBytes((byte) 0, (byte) 0, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
            }

        } catch (SocketException e) {
            // Generate random node ID
            nodeId = RANDOM.nextLong() & 0xFFFFFFL | 0x100000L;
        }

        long lowerLong = ((long) clockId | 0x8000) << 48 | nodeId;

        return new java.util.UUID(upperLong, lowerLong);
    }

    private static class RunIdImpl implements RunId {
        private final UUID id;

        public RunIdImpl(UUID id) {
            this.id = id;
        }

        @Override
        public String getId() {
            return id.toString();
        }

        @Override
        public String toString() {
            return id.toString();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            RunIdImpl that = (RunIdImpl) o;

            return Objects.equal(this.id, that.id);
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }
}