Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
import org.hibernate.internal.build.AllowSysOut;
import org.hibernate.internal.util.BytesHelper;

import static java.lang.System.arraycopy;
import static java.lang.System.currentTimeMillis;
import static org.hibernate.id.uuid.Helper.getAddressBytes;
import static org.hibernate.id.uuid.Helper.getCountBytes;
import static org.hibernate.id.uuid.Helper.getJvmIdentifierBytes;

/**
* Applies a version 1 (time-based) generation strategy (using ip address rather than mac address) but applies them in a
* different layout. The strategy is very similar to the legacy {@link org.hibernate.id.UUIDHexGenerator} id generator
Expand All @@ -31,22 +37,21 @@ public int getGeneratedVersion() {

public CustomVersionOneStrategy() {
// generate the "most significant bits" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
byte[] hiBits = new byte[8];
// use address as first 32 bits (8 * 4 bytes)
System.arraycopy( Helper.getAddressBytes(), 0, hiBits, 0, 4 );
final byte[] hiBits = new byte[8];
// use address as the first 32 bits (8 * 4 bytes)
arraycopy( getAddressBytes(), 0, hiBits, 0, 4 );
// use the "jvm identifier" as the next 32 bits
System.arraycopy( Helper.getJvmIdentifierBytes(), 0, hiBits, 4, 4 );
arraycopy( getJvmIdentifierBytes(), 0, hiBits, 4, 4 );
// set the version (rfc term) appropriately
hiBits[6] &= 0x0f;
hiBits[6] |= 0x10;

mostSignificantBits = BytesHelper.asLong( hiBits );
}

@Override
public UUID generateUuid(SharedSessionContractImplementor session) {
long leastSignificantBits = generateLeastSignificantBits( System.currentTimeMillis() );
return new UUID( mostSignificantBits, leastSignificantBits );
return new UUID( mostSignificantBits,
generateLeastSignificantBits( currentTimeMillis() ) );
}

@Override
Expand All @@ -59,33 +64,31 @@ public long getMostSignificantBits() {
}

public static long generateLeastSignificantBits(long seed) {
byte[] loBits = new byte[8];

short hiTime = (short) ( seed >>> 32 );
int loTime = (int) seed;
System.arraycopy( BytesHelper.fromShort( hiTime ), 0, loBits, 0, 2 );
System.arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 );
System.arraycopy( Helper.getCountBytes(), 0, loBits, 6, 2 );
final byte[] loBits = new byte[8];
final short hiTime = (short) ( seed >>> 32 );
final int loTime = (int) seed;
arraycopy( BytesHelper.fromShort( hiTime ), 0, loBits, 0, 2 );
arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 );
arraycopy( getCountBytes(), 0, loBits, 6, 2 );
loBits[0] &= 0x3f;
loBits[0] |= ((byte)2 << (byte)6 );

loBits[0] |= ((byte)2 << (byte)6);
return BytesHelper.asLong( loBits );
}

@AllowSysOut
public static void main(String[] args) {
CustomVersionOneStrategy strategy = new CustomVersionOneStrategy();
final var strategy = new CustomVersionOneStrategy();

for ( int i = 0; i < 1000; i++ ) {
System.out.println( "Generation # " + i + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
byte[] loBits = new byte[8];

long sysTime = System.currentTimeMillis();
short hiTime = (short) ( System.currentTimeMillis() >>> 32 );
long sysTime = currentTimeMillis();
short hiTime = (short) ( currentTimeMillis() >>> 32 );
int loTime = (int) sysTime;
System.arraycopy( BytesHelper.fromShort( hiTime ), 0, loBits, 0, 2 );
System.arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 );
System.arraycopy( Helper.getCountBytes(), 0, loBits, 6, 2 );
arraycopy( BytesHelper.fromShort( hiTime ), 0, loBits, 0, 2 );
arraycopy( BytesHelper.fromInt( loTime ), 0, loBits, 2, 4 );
arraycopy( getCountBytes(), 0, loBits, 6, 2 );

System.out.println( " before bit setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
System.out.println( " loBits[0] : " + BytesHelper.toBinaryString( loBits[0] ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ public static byte[] getCountBytes() {
// Helper methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public static String format(int value) {
final String formatted = Integer.toHexString( value );
StringBuilder buf = new StringBuilder( "00000000" );
final var formatted = Integer.toHexString( value );
final var buf = new StringBuilder( "00000000" );
buf.replace( 8 - formatted.length(), 8, formatted );
return buf.toString();
}

public static String format(short value) {
String formatted = Integer.toHexString( value );
StringBuilder buf = new StringBuilder( "0000" );
final var formatted = Integer.toHexString( value );
final var buf = new StringBuilder( "0000" );
buf.replace( 4 - formatted.length(), 4, formatted );
return buf.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,15 @@ public UuidGenerator(
org.hibernate.annotations.UuidGenerator config,
MemberDetails memberDetails) {
generator = determineValueGenerator( config, memberDetails.getDeclaringType().getName(), memberDetails.getName() );

final Class<?> memberType = memberDetails.getType().determineRawClass().toJavaClass();
valueTransformer = determineProperTransformer( memberType );
valueTransformer = determineProperTransformer( memberDetails.getType().determineRawClass().toJavaClass() );
}

@Internal
public UuidGenerator(
org.hibernate.annotations.UuidGenerator config,
Member idMember) {
generator = determineValueGenerator( config, idMember.getDeclaringClass().getName(), idMember.getName() );

final Class<?> propertyType = getPropertyType( idMember );
this.valueTransformer = determineProperTransformer( propertyType );
valueTransformer = determineProperTransformer( getPropertyType( idMember ) );
}

public UuidGenerator(
Expand Down Expand Up @@ -106,39 +102,34 @@ private static UuidValueGenerator determineValueGenerator(
org.hibernate.annotations.UuidGenerator config,
String memberDeclaringClassName,
String memberName) {
if ( config != null ) {
if ( config == null ) {
return StandardRandomStrategy.INSTANCE;
}
else {
// there is an annotation
final var style = config.style();
if ( config.algorithm() != UuidValueGenerator.class ) {
// the annotation specified a custom algorithm
if ( config.style() != AUTO ) {
if ( style != AUTO ) {
throw new MappingException(
String.format(
Locale.ROOT,
"Style [%s] should not be specified with custom UUID value generator : %s.%s",
config.style().name(),
"Style [%s] should not be specified with custom UUID value generator: %s.%s",
style.name(),
memberDeclaringClassName,
memberName
)
);
}
return instantiateCustomGenerator( config.algorithm() );
}
if ( config.style() == TIME ) {
return new CustomVersionOneStrategy();
}
if ( config.style() == VERSION_6 ) {
return UuidVersion6Strategy.INSTANCE;
}
if ( config.style() == VERSION_7 ) {
return UuidVersion7Strategy.INSTANCE;
}
// NOTE : AUTO falls through
return switch ( style ) {
case TIME -> new CustomVersionOneStrategy();
case VERSION_6 -> UuidVersion6Strategy.INSTANCE;
case VERSION_7 -> UuidVersion7Strategy.INSTANCE;
default -> StandardRandomStrategy.INSTANCE;
};
}

// Either -
// 1. there is no annotation
// 2. the annotation specified AUTO (with no custom algorithm)
return StandardRandomStrategy.INSTANCE;
}

private static UuidValueGenerator instantiateCustomGenerator(Class<? extends UuidValueGenerator> algorithmClass) {
Expand All @@ -154,15 +145,14 @@ private ValueTransformer determineProperTransformer(Class<?> propertyType) {
if ( UUID.class.isAssignableFrom( propertyType ) ) {
return UUIDJavaType.PassThroughTransformer.INSTANCE;
}

if ( String.class.isAssignableFrom( propertyType ) ) {
else if ( String.class.isAssignableFrom( propertyType ) ) {
return UUIDJavaType.ToStringTransformer.INSTANCE;
}

if ( byte[].class.isAssignableFrom( propertyType ) ) {
else if ( byte[].class.isAssignableFrom( propertyType ) ) {
return UUIDJavaType.ToBytesTransformer.INSTANCE;
}

throw new HibernateException( "Unanticipated return type [" + propertyType.getName() + "] for UUID conversion" );
else {
throw new HibernateException( "Unanticipated return type [" + propertyType.getName() + "] for UUID conversion" );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,16 @@ public static class Holder {
static final long EPOCH_1582_SECONDS = LocalDate.of( 1582, 10, 15 )
.atStartOfDay( ZoneId.of( "UTC" ) )
.toInstant().getEpochSecond();

}

private record State(long lastTimestamp, int lastSequence) {
public State getNextState() {
final long now = instantToTimestamp();
if ( this.lastTimestamp < now ) {
return new State(
now,
randomSequence()
);
if ( lastTimestamp < now ) {
return new State( now, randomSequence() );
}
else if ( lastSequence == 0x3FFF ) {
return new State(
this.lastTimestamp + 1,
randomSequence()
);
return new State( lastTimestamp + 1, randomSequence() );
}
else {
return new State( lastTimestamp, lastSequence + 1 );
Expand All @@ -68,7 +61,7 @@ private static int randomSequence() {
}

private static long instantToTimestamp() {
final Instant instant = Instant.now();
final var instant = Instant.now();
final long seconds = instant.getEpochSecond() - Holder.EPOCH_1582_SECONDS;
return seconds * 10_000_000 + instant.getNano() / 100;
}
Expand Down Expand Up @@ -101,20 +94,19 @@ public UUID generateUUID(final SharedSessionContractImplementor session) {

@Override
public UUID generateUuid(final SharedSessionContractImplementor session) {
final State state = lastState.updateAndGet( State::getNextState );

final var state = lastState.updateAndGet( State::getNextState );
return new UUID(
// MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
// MSB bits 0-47 - the most significant 32 bits of the 60-bit starting timestamp
state.lastTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
// MSB bits 48-51 - version = 6
| 0x6000L
// MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
// MSB bits 52-63 - the least significant 12 bits from the 60-bit starting timestamp
| state.lastTimestamp & 0x0FFFL,
// LSB bits 0-1 - variant = 4
0x8000_0000_0000_0000L
// LSB bits 2-15 - clock sequence
| (long) state.lastSequence << 48
// LSB bits 16-63 - pseudorandom data, least significant bit of the first octet is set to 1
// LSB bits 16-63 - pseudorandom data, the least significant bit of the first octet is set to 1
| randomNode()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe
@Internal
public static class Holder {
private static final SecureRandom numberGenerator = new SecureRandom();

}

public record State(Instant lastTimestamp, long lastSequence, long nanos) {
Expand All @@ -60,20 +59,23 @@ private static long nanos(Instant timestamp) {
}

public State getNextState() {
final Instant now = Instant.now();
if ( lastTimestamp.toEpochMilli() < now.toEpochMilli() ||
lastTimestamp.toEpochMilli() == now.toEpochMilli() && nanos < nanos( now ) ) {
final var now = Instant.now();
if ( lastTimestampEarlierThan( now ) ) {
return new State( now, randomSequence() );
}
final long nextSequence = lastSequence + Holder.numberGenerator.nextLong( 0xFFFF_FFFFL );
if ( nextSequence > MAX_RANDOM_SEQUENCE ) {
return new State( lastTimestamp.plusNanos( 250 ), randomSequence() );
}
else {
return new State( lastTimestamp, nextSequence );
final long nextSequence = lastSequence + Holder.numberGenerator.nextLong( 0xFFFF_FFFFL );
return nextSequence > MAX_RANDOM_SEQUENCE
? new State( lastTimestamp.plusNanos( 250 ), randomSequence() )
: new State( lastTimestamp, nextSequence );
}
}

private boolean lastTimestampEarlierThan(Instant now) {
return lastTimestamp.toEpochMilli() < now.toEpochMilli()
|| lastTimestamp.toEpochMilli() == now.toEpochMilli() && nanos < nanos( now );
}

private static long randomSequence() {
return Holder.numberGenerator.nextLong( MAX_RANDOM_SEQUENCE );
}
Expand Down Expand Up @@ -106,8 +108,7 @@ public UUID generateUUID(final SharedSessionContractImplementor session) {

@Override
public UUID generateUuid(final SharedSessionContractImplementor session) {
final State state = lastState.updateAndGet( State::getNextState );

final var state = lastState.updateAndGet( State::getNextState );
return new UUID(
// MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds
state.millis() << 16 & 0xFFFF_FFFF_FFFF_0000L
Expand Down