From 593a4eabb76b74cd382ecf3f181d57abe0d643f9 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 26 Jan 2022 06:47:10 +0100 Subject: [PATCH] All typed UUIDs implemented (except v2). --- .../api/uuid/AbstractAsyncUuidFactory.java | 20 +++---- .../argeo/api/uuid/AbstractUuidFactory.java | 19 +++++-- ...UnkownNameUuid.java => BasicNameUuid.java} | 6 +- .../org/argeo/api/uuid/BinaryNameUuid.java | 56 +++++++++++++++++++ .../argeo/api/uuid/ConcurrentUuidFactory.java | 4 +- .../src/org/argeo/api/uuid/GUID.java | 52 +++++++++++++++++ .../src/org/argeo/api/uuid/NameUuid.java | 30 ++++++++-- .../src/org/argeo/api/uuid/RandomUuid.java | 1 + .../src/org/argeo/api/uuid/TimeUuid.java | 1 + .../src/org/argeo/api/uuid/TypedUuid.java | 16 ++++-- .../org/argeo/api/uuid/TypedUuidFactory.java | 26 +++++++++ .../src/org/argeo/cms/acr/CmsUuidFactory.java | 2 +- 12 files changed, 200 insertions(+), 33 deletions(-) rename org.argeo.api.uuid/src/org/argeo/api/uuid/{UnkownNameUuid.java => BasicNameUuid.java} (81%) create mode 100644 org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java create mode 100644 org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java create mode 100644 org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java index 685fe0a07..38327223f 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java @@ -28,14 +28,14 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple private NodeIdSupplier nodeIdSupplier; public AbstractAsyncUuidFactory() { - secureRandom = newSecureRandom(); + secureRandom = createSecureRandom(); timeUuidState = new ConcurrentTimeUuidState(secureRandom, null); } /* * ABSTRACT METHODS */ - protected abstract SecureRandom newSecureRandom(); + protected abstract SecureRandom createSecureRandom(); /* * STATE @@ -59,7 +59,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple /* * SYNC OPERATIONS */ - protected UUID newRandomUUIDStrong() { + protected UUID createRandomUUIDStrong() { SecureRandomParameters parameters = secureRandom.getParameters(); if (parameters != null) { if (parameters instanceof DrbgParameters.Instantiation) { @@ -70,14 +70,14 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple } } } - return newRandomUUID(secureRandom); + return createRandomUUID(secureRandom); } public UUID randomUUIDWeak() { - return newRandomUUID(ThreadLocalRandom.current()); + return createRandomUUID(ThreadLocalRandom.current()); } - protected UUID newTimeUUID() { + protected UUID createTimeUUID() { if (nodeIdSupplier == null) throw new IllegalStateException("No node id supplier available"); UUID uuid = new UUID(timeUuidState.getMostSignificantBits(), timeUuidState.getLeastSignificantBits()); @@ -126,21 +126,21 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple @Override public ForkJoinTask futureNameUUIDv5(UUID namespace, byte[] data) { - return submit(() -> newNameUUIDv5(namespace, data)); + return submit(() -> createNameUUIDv5(namespace, data)); } @Override public ForkJoinTask futureNameUUIDv3(UUID namespace, byte[] data) { - return submit(() -> newNameUUIDv3(namespace, data)); + return submit(() -> createNameUUIDv3(namespace, data)); } @Override public ForkJoinTask futureRandomUUIDStrong() { - return submit(this::newRandomUUIDStrong); + return submit(this::createRandomUUIDStrong); } @Override public ForkJoinTask futureTimeUUID() { - return submit(this::newTimeUUID); + return submit(this::createTimeUUID); } } diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java index 75a6339b0..1bbc4391f 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java @@ -23,7 +23,8 @@ public abstract class AbstractUuidFactory implements UuidFactory { private final static long MOST_SIG_VERSION1 = (1l << 12); private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63); - protected UUID newTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) { + @Deprecated + protected UUID createTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) { Objects.requireNonNull(node, "Node array cannot be null"); if (node.length < offset + 6) throw new IllegalArgumentException("Node array must be at least 6 bytes long"); @@ -53,19 +54,23 @@ public abstract class AbstractUuidFactory implements UuidFactory { return uuid; } + @Deprecated protected UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) { // TODO add checks Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, time); // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000 long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100; - return newTimeUUID(timestamp, clockSequence, node, offset); + return createTimeUUID(timestamp, clockSequence, node, offset); } /* * NAME BASED (version 3 and 5) */ + protected UUID createNameUUIDv5(UUID namespace, byte[] name) { + return createNameUUIDv5Static(namespace, name); + } - protected UUID newNameUUIDv5(UUID namespace, byte[] name) { + static UUID createNameUUIDv5Static(UUID namespace, byte[] name) { Objects.requireNonNull(namespace, "Namespace cannot be null"); Objects.requireNonNull(name, "Name cannot be null"); @@ -78,7 +83,11 @@ public abstract class AbstractUuidFactory implements UuidFactory { return result; } - protected UUID newNameUUIDv3(UUID namespace, byte[] name) { + protected UUID createNameUUIDv3(UUID namespace, byte[] name) { + return createNameUUIDv3Static(namespace, name); + } + + static UUID createNameUUIDv3Static(UUID namespace, byte[] name) { Objects.requireNonNull(namespace, "Namespace cannot be null"); Objects.requireNonNull(name, "Name cannot be null"); @@ -91,7 +100,7 @@ public abstract class AbstractUuidFactory implements UuidFactory { /* * RANDOM v4 */ - protected UUID newRandomUUID(Random random) { + protected UUID createRandomUUID(Random random) { byte[] arr = new byte[16]; random.nextBytes(arr); arr[6] &= 0x0f; diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java similarity index 81% rename from org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java rename to org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java index 623de91f6..b883e0dcb 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/UnkownNameUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java @@ -2,11 +2,11 @@ package org.argeo.api.uuid; import java.util.UUID; -/** A name-based UUID (v3 or v5) whose construction values are not known. */ -public class UnkownNameUuid extends TypedUuid { +/** A name based UUID (v3 or v5) whose construction values are not known. */ +public class BasicNameUuid extends TypedUuid { private static final long serialVersionUID = APM.SERIAL; - public UnkownNameUuid(UUID uuid) { + public BasicNameUuid(UUID uuid) { super(uuid); if ((uuid.version() != 5 && uuid.version() != 3) || uuid.variant() != 2) throw new IllegalArgumentException("The provided UUID is not a name-based UUID."); diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java new file mode 100644 index 000000000..6771c7e72 --- /dev/null +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java @@ -0,0 +1,56 @@ +package org.argeo.api.uuid; + +import java.util.UUID; + +/** + * A name UUID whose binary data used for its construction is known. A new byte + * array is created and it is copied when retrieved, so this would be + * inefficient and memory consuming for big data amounts. This rather meant to + * be used for small binary data, such as certificates, etc. + */ +public class BinaryNameUuid extends BasicNameUuid { + private static final long serialVersionUID = APM.SERIAL; + + protected final TypedUuid namespace; + protected final byte[] bytes; + + /** Static builder (a {@link TypedUuidFactory} may be more efficient). */ + public BinaryNameUuid(TypedUuid namespace, byte[] bytes, boolean sha1) { + this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes) + : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes), namespace, bytes); + } + + /** + * Since no check is performed, the constructor is protected so that the object + * can only be built by the default methods of {@link TypedUuidFactory} (in this + * package) or by extending the class. + */ + protected BinaryNameUuid(UUID uuid, TypedUuid namespace, byte[] bytes) { + super(uuid); + this.namespace = namespace; + this.bytes = new byte[bytes.length]; + System.arraycopy(bytes, 0, this.bytes, 0, bytes.length); + } + + /** The namespace used to build this UUID. */ + public final TypedUuid getNamespace() { + return namespace; + } + + /** + * A copy of the bytes which have been hashed. In order to + * access the byte array directly, the class must be extended. + */ + public final byte[] getBytes() { + byte[] bytes = new byte[this.bytes.length]; + System.arraycopy(this.bytes, 0, bytes, 0, this.bytes.length); + return bytes; + } + + /** Always returns false since the construction value is known. */ + @Override + public final boolean isOpaque() { + return false; + } + +} diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java index 6debd83b8..15245b293 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java @@ -16,7 +16,7 @@ import java.util.UUID; * * @see https://datatracker.ietf.org/doc/html/rfc4122 */ -public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory { +public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory { private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName()); public ConcurrentUuidFactory(byte[] nodeId) { @@ -60,7 +60,7 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory { } @Override - protected SecureRandom newSecureRandom() { + protected SecureRandom createSecureRandom() { SecureRandom secureRandom; try { secureRandom = SecureRandom.getInstance("DRBG", diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java new file mode 100644 index 000000000..a9a5af17d --- /dev/null +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java @@ -0,0 +1,52 @@ +package org.argeo.api.uuid; + +import java.util.UUID; + +/** + * A variant 6 {@link UUID}. + * + * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1 + */ +public class GUID extends TypedUuid { + private static final long serialVersionUID = APM.SERIAL; + + /** Constructor based on a {@link UUID}. */ + public GUID(UUID uuid) { + super(uuid); + if (uuid.variant() != 6) + throw new IllegalArgumentException("The provided UUID is not a GUID."); + } + + /** + * Formats N, D, B, P are supported: + *
    + *
  • D: 1db31359-bdd8-5a0f-b672-30c247d582c5
  • + *
  • N: 1db31359bdd85a0fb67230c247d582c5
  • + *
  • B: {1db31359-bdd8-5a0f-b672-30c247d582c5}
  • + *
  • P: (1db31359-bdd8-5a0f-b672-30c247d582c5)
  • + *
+ * + * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring + */ + public static String toString(UUID uuid, char format, boolean upperCase) { + String str; + switch (format) { + case 'D': + str = uuid.toString(); + break; + case 'N': + str = UuidBinaryUtils.toCompact(uuid); + break; + case 'B': + str = "{" + uuid.toString() + "}"; + break; + case 'P': + str = "(" + uuid.toString() + ")"; + break; + default: + throw new IllegalArgumentException("Unsupported format : " + format); + } + return upperCase ? str.toUpperCase() : str; + } + +} diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java index a1c64a1a1..376eea4bc 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java @@ -1,16 +1,38 @@ package org.argeo.api.uuid; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.UUID; -/** A name {@link UUID} whose values used for its construction are known. */ -public class NameUuid extends UnkownNameUuid { +/** A name UUID whose values used for its construction are known. */ +public class NameUuid extends BasicNameUuid { private static final long serialVersionUID = APM.SERIAL; protected final TypedUuid namespace; protected final String name; protected final Charset encoding; + /** + * Default static builder which creates a v5 (SHA1) name based {@link UUID}, + * using UTF-8 encoding. Use + * {@link #NameUuid(TypedUuid, String, Charset, boolean)} for more options. + */ + public NameUuid(TypedUuid namespace, String name) { + this(namespace, name, StandardCharsets.UTF_8, true); + } + + /** Static builder (an {@link TypedUuidFactory} may be more efficient). */ + public NameUuid(TypedUuid namespace, String name, Charset encoding, boolean sha1) { + this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding)) + : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding)), namespace, name, + encoding); + } + + /** + * Since no check is performed, the constructor is protected so that the object + * can only be built by the default methods of {@link TypedUuidFactory} (in this + * package) or by extending the class. + */ protected NameUuid(UUID uuid, TypedUuid namespace, String name, Charset encoding) { super(uuid); assert namespace != null; @@ -21,10 +43,6 @@ public class NameUuid extends UnkownNameUuid { this.encoding = encoding; } - public static long getSerialversionuid() { - return serialVersionUID; - } - /** The namespace used to build this UUID. */ public final TypedUuid getNamespace() { return namespace; diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java index ea71c4833..b65772e9b 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java @@ -6,6 +6,7 @@ import java.util.UUID; public final class RandomUuid extends TypedUuid { private static final long serialVersionUID = APM.SERIAL; + /** Constructor based on a {@link UUID}. */ public RandomUuid(UUID uuid) { super(uuid); if (uuid.version() != 4 && uuid.variant() != 2) diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java index 2f3a73fff..2276cd268 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java @@ -18,6 +18,7 @@ public class TimeUuid extends TypedUuid { */ public final static Instant TIMESTAMP_ZERO = ZonedDateTime.of(1582, 10, 15, 0, 0, 0, 0, ZoneOffset.UTC).toInstant(); + /** Constructor based on a {@link UUID}. */ public TimeUuid(UUID uuid) { super(uuid); if (uuid.version() != 1 && uuid.variant() != 2) diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java index 55a67abef..2a0842fe6 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java @@ -4,8 +4,9 @@ import java.util.Objects; import java.util.UUID; /** - * Base class for objects which are explicitly typed, based on the various UUID - * versions. Such a derivation hierarchy still represents the {@link UUID} + * Base class for objects which are explicitly typed, based on the various + * variant 2 (RFC 4122) UUID versions (and variant 6 with {@link GUID}, for + * completion). Such a derivation hierarchy still represents the {@link UUID} * itself, not the objects, data or concept that it identifies. Just like * {@link UUID}s, {@link TypedUuid} should be used as identifier, not as base * class for complex objects. It should rather be seen as a framework to build @@ -35,7 +36,9 @@ public abstract class TypedUuid extends UuidHolder { /** * Constructs a {@link TypedUuid} of the most appropriate subtype, based on this - * {@link UUID}. + * {@link UUID}. For name based UUIDs, it will return an opaque + * {@link BasicNameUuid}; {@link NameUuid} and {@link BinaryNameUuid} may be + * more useful. */ public static TypedUuid of(UUID uuid) { Objects.requireNonNull(uuid, "UUID cannot be null"); @@ -47,14 +50,15 @@ public abstract class TypedUuid extends UuidHolder { return new RandomUuid(uuid); case 3: case 5: - return new UnkownNameUuid(uuid); + return new BasicNameUuid(uuid); default: throw new IllegalArgumentException("UUIDs with version " + uuid.version() + " are not supported."); } - } else if (uuid.variant() == 6) {// Microsoft - throw new IllegalArgumentException("Microsoft UUIDs (aka. GUIDs) are not supported."); + } else if (uuid.variant() == 6) {// GUID + return new GUID(uuid); } else { throw new IllegalArgumentException("UUIDs with variant " + uuid.variant() + " are not supported."); } } + } diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java new file mode 100644 index 000000000..770e9b782 --- /dev/null +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java @@ -0,0 +1,26 @@ +package org.argeo.api.uuid; + +import java.nio.charset.Charset; + +/** An {@link UuidFactory} which also (trivially) produces {@link TypedUuid}. */ +public interface TypedUuidFactory extends UuidFactory { + /** Creates a {@link TimeUuid} (v1). */ + default TimeUuid newTimeUuid() { + return new TimeUuid(timeUUID()); + } + + /** Creates an MD5 {@link NameUuid} (v3). */ + default NameUuid newNameUuidV3(TypedUuid namespace, String name, Charset charset) { + return new NameUuid(nameUUIDv3(namespace.get(), name, charset), namespace, name, charset); + } + + /** Creates a {@link RandomUuid}, using {@link #randomUUID()}. */ + default RandomUuid newRandomUuid() { + return new RandomUuid(randomUUID()); + } + + /** Creates an SHA1 {@link NameUuid} (v5). */ + default NameUuid newNameUuidV5(TypedUuid namespace, String name, Charset charset) { + return new NameUuid(nameUUIDv5(namespace.get(), name, charset), namespace, name, charset); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java index de0be3661..3ce27fce1 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java @@ -18,7 +18,7 @@ public class CmsUuidFactory extends ConcurrentUuidFactory { public CmsUuidFactory(byte[] nodeId) { super(nodeId); - assert newTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0]; + assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0]; } public CmsUuidFactory() { -- 2.30.2