io.stargate.sgv2.graphql.schema.Uuids Maven / Gradle / Ivy
/*
* Copyright The Stargate Authors
*
* 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 io.stargate.sgv2.graphql.schema;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* Provides utility methods to handle time-based UUIDs.
*
* Adapted from the Java
* driver class of the same name (also under Apache v2 license).
*/
public class Uuids {
private static final long START_EPOCH = makeEpoch();
private static final long CLOCK_SEQ_AND_NODE = makeClockSeqAndNode();
private static final AtomicLong lastTimestamp = new AtomicLong(0L);
private Uuids() {
// nothing to do
}
/**
* Creates a new time-based (version 1) UUID.
*
*
UUIDs generated by this method are suitable for use with the {@code timeuuid} Cassandra
* type. In particular the generated UUID includes the timestamp of its generation.
*/
public static UUID timeBased() {
return new UUID(makeMsb(getCurrentTimestamp()), CLOCK_SEQ_AND_NODE);
}
/**
* Returns the Unix timestamp contained by the provided time-based UUID.
*
*
This method is not equivalent to {@link UUID#timestamp()}. More precisely, a version 1 UUID
* stores a timestamp that represents the number of 100-nanoseconds intervals since midnight, 15
* October 1582 and that is what {@link UUID#timestamp()} returns. This method however converts
* that timestamp to the equivalent Unix timestamp in milliseconds, i.e. a timestamp representing
* a number of milliseconds since midnight, January 1, 1970 UTC. In particular, the timestamps
* returned by this method are comparable to the timestamps returned by {@link
* System#currentTimeMillis}, {@link Date#getTime}, etc.
*
* @throws IllegalArgumentException if {@code uuid} is not a version 1 UUID.
*/
public static long unixTimestamp(UUID uuid) {
if (uuid.version() != 1) {
throw new IllegalArgumentException(
String.format(
"Can only retrieve the unix timestamp for version 1 uuid (provided version %d)",
uuid.version()));
}
long timestamp = uuid.timestamp();
return (timestamp / 10000) + START_EPOCH;
}
private static long makeMsb(long timestamp) {
long msb = 0L;
msb |= (0x00000000ffffffffL & timestamp) << 32;
msb |= (0x0000ffff00000000L & timestamp) >>> 16;
msb |= (0x0fff000000000000L & timestamp) >>> 48;
msb |= 0x0000000000001000L; // sets the version to 1.
return msb;
}
// Use {@link System#currentTimeMillis} for a base time in milliseconds, and if we are in the same
// millisecond as the previous generation, increment the number of nanoseconds.
// However, since the precision is 100-nanosecond intervals, we can only generate 10K UUIDs within
// a millisecond safely. If we detect we have already generated that much UUIDs within a
// millisecond (which, while admittedly unlikely in a real application, is very achievable on even
// modest machines), then we stall the generator (busy spin) until the next millisecond as
// required by the RFC.
private static long getCurrentTimestamp() {
while (true) {
long now = fromUnixTimestamp(System.currentTimeMillis());
long last = lastTimestamp.get();
if (now > last) {
if (lastTimestamp.compareAndSet(last, now)) {
return now;
}
} else {
long lastMillis = millisOf(last);
// If the clock went back in time, bail out
if (millisOf(now) < millisOf(last)) {
return lastTimestamp.incrementAndGet();
}
long candidate = last + 1;
// If we've generated more than 10k uuid in that millisecond, restart the whole process
// until we get to the next millis. Otherwise, we try use our candidate ... unless we've
// been beaten by another thread in which case we try again.
if (millisOf(candidate) == lastMillis && lastTimestamp.compareAndSet(last, candidate)) {
return candidate;
}
}
}
}
private static long fromUnixTimestamp(long tstamp) {
return (tstamp - START_EPOCH) * 10000;
}
private static long millisOf(long timestamp) {
return timestamp / 10000;
}
private static long makeEpoch() {
// UUID v1 timestamps must be in 100-nanoseconds interval since 00:00:00.000 15 Oct 1582.
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT-0"));
c.set(Calendar.YEAR, 1582);
c.set(Calendar.MONTH, Calendar.OCTOBER);
c.set(Calendar.DAY_OF_MONTH, 15);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
}
private static long makeClockSeqAndNode() {
long clock = new Random(System.currentTimeMillis()).nextLong();
long node = makeNode();
long lsb = 0;
lsb |= (clock & 0x0000000000003FFFL) << 48;
lsb |= 0x8000000000000000L;
lsb |= node;
return lsb;
}
private static long makeNode() {
// We don't have access to the MAC address (in pure JAVA at least) but need to generate a node
// part that identifies this host as uniquely as possible.
// The spec says that one option is to take as many sources that identify this node as possible
// and hash them together. That's what we do here by gathering all the IPs of this host as well
// as a few other sources.
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
for (String address : getAllLocalAddresses()) update(digest, address);
Properties props = System.getProperties();
update(digest, props.getProperty("java.vendor"));
update(digest, props.getProperty("java.vendor.url"));
update(digest, props.getProperty("java.version"));
update(digest, props.getProperty("os.arch"));
update(digest, props.getProperty("os.name"));
update(digest, props.getProperty("os.version"));
update(digest, getProcessPiece());
byte[] hash = digest.digest();
long node = 0;
for (int i = 0; i < 6; i++) node |= (0x00000000000000ffL & (long) hash[i]) << (i * 8);
// Since we don't use the MAC address, the spec says that the multicast bit (least significant
// bit of the first byte of the node ID) must be 1.
return node | 0x0000010000000000L;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private static Set getAllLocalAddresses() {
Set allIps = new HashSet<>();
try {
InetAddress localhost = InetAddress.getLocalHost();
allIps.add(localhost.toString());
// Also return the hostname if available, it won't hurt (this does a dns lookup, it's only
// done once at startup)
allIps.add(localhost.getCanonicalHostName());
InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName());
if (allMyIps != null) {
for (InetAddress allMyIp : allMyIps) {
allIps.add(allMyIp.toString());
}
}
} catch (UnknownHostException e) {
// Ignore, we'll try the network interfaces anyway
}
try {
Enumeration en = NetworkInterface.getNetworkInterfaces();
if (en != null) {
while (en.hasMoreElements()) {
Enumeration enumIpAddr = en.nextElement().getInetAddresses();
while (enumIpAddr.hasMoreElements()) {
allIps.add(enumIpAddr.nextElement().toString());
}
}
}
} catch (SocketException e) {
// Ignore, if we've really got nothing so far, we'll throw an exception
}
return allIps;
}
private static void update(MessageDigest digest, String value) {
if (value != null) {
digest.update(value.getBytes(StandardCharsets.UTF_8));
}
}
private static String getProcessPiece() {
int pid;
try {
@SuppressWarnings("StringSplitter")
String pidJmx = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
pid = Integer.parseInt(pidJmx);
} catch (Exception e) {
pid = new Random().nextInt();
}
ClassLoader loader = Uuids.class.getClassLoader();
int loaderId = loader != null ? System.identityHashCode(loader) : 0;
return Integer.toHexString(pid) + Integer.toHexString(loaderId);
}
}